package main

import (
	"context"
	"embed"
	"errors"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/gin-gonic/gin"
)

//go:embed migrations/*.sql
var migrations embed.FS
var db *Db

// ServerConfig holds configuration for the server.
type ServerConfig struct {
	// The datasource to the SQLite database.
	// Use ":memory:" for an in-memory database.
	Datasource string

	// The port to listen on.
	// Use an empty string to listen on a random port.
	Port string

	// The channel that gets signaled when the server has started.
	Started chan bool
}

func getUsers(c *gin.Context) {
	users, err := db.GetUsers()
	if err != nil {
		log.Printf("Error getting users: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
		return
	}
	c.IndentedJSON(http.StatusOK, users)
}

func getUser(c *gin.Context) {
	userIdStr := c.Param("userId")
	userId, err := strconv.Atoi(userIdStr)
	if err != nil {
		log.Printf("Invalid user ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	user, err := db.GetUser(userId)
	if err != nil {
		log.Printf("Error getting user: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
		return
	}
	if user == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	c.IndentedJSON(http.StatusOK, user)
}

func getUserGoals(c *gin.Context) {
	userIdStr := c.Param("userId")
	userId, err := strconv.Atoi(userIdStr)
	if err != nil {
		log.Printf("Invalid user ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	exists, err := db.UserExists(userId)
	if err != nil {
		log.Printf("Error checking user existence: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
		return
	}
	if !exists {
		log.Printf("Error checking user existence: %v", err)
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	goals, err := db.GetUserGoals(userId)
	if err != nil {
		log.Printf("Error getting user goals: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
		return
	}
	c.IndentedJSON(http.StatusOK, goals)
}

func createUserGoal(c *gin.Context) {
	userIdStr := c.Param("userId")
	userId, err := strconv.Atoi(userIdStr)
	if err != nil {
		log.Printf("Invalid user ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
		return
	}

	// Parse request body
	var goalRequest CreateGoalRequest
	if err := c.ShouldBindJSON(&goalRequest); err != nil {
		log.Printf("Error parsing request body: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	// Validate request
	if goalRequest.Name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Goal name cannot be empty"})
		return
	}

	// Create goal in database
	goalId, err := db.CreateGoal(userId, &goalRequest)
	if err != nil {
		log.Printf("Error creating goal: %v", err)
		if err.Error() == "user does not exist" {
			c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		} else {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Could not create goal"})
		}
		return
	}

	// Return created goal ID
	response := CreateGoalResponse{ID: goalId}
	c.IndentedJSON(http.StatusCreated, response)
}

/*
*
Initialises the database, and then starts the server.
If the context gets cancelled, the server is shutdown and the database is closed.
*/
func start(ctx context.Context, config *ServerConfig) {
	db = NewDb(config.Datasource)
	defer db.db.MustClose()

	router := gin.Default()
	router.GET("/api/users", getUsers)
	router.GET("/api/user/:userId", getUser)
	router.GET("/api/user/:userId/goals", getUserGoals)
	router.POST("/api/user/:userId/goals", createUserGoal)

	srv := &http.Server{
		Addr:    ":" + config.Port,
		Handler: router.Handler(),
	}
	go func() {
		if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	log.Printf("Running server on port %s\n", config.Port)
	if config.Started != nil {
		config.Started <- true
	}
	<-ctx.Done()
	log.Println("Shutting down")
	if err := srv.Shutdown(context.Background()); err != nil {
		log.Fatalf("Server forced to shutdown: %v", err)
	}
}

func main() {
	config := ServerConfig{
		Datasource: os.Getenv("DB_PATH"),
	}
	start(context.Background(), &config)
}