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

const (
	ErrInternalServerError = "Internal Server Error"
	ErrInvalidUserID       = "Invalid user ID"
	ErrUserNotFound        = "User not found"
	ErrCheckingUserExist   = "Error checking user existence: %v"
)

// 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": ErrInternalServerError})
		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(ErrInvalidUserID+": %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": ErrInvalidUserID})
		return
	}

	user, err := db.GetUser(userId)
	if err != nil {
		log.Printf("Error getting user: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}
	if user == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound})
		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(ErrInvalidUserID+": %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": ErrInvalidUserID})
		return
	}

	exists, err := db.UserExists(userId)
	if err != nil {
		log.Printf(ErrCheckingUserExist, err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}
	if !exists {
		log.Printf(ErrCheckingUserExist, err)
		c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound})
		return
	}

	goals, err := db.GetUserGoals(userId)
	if err != nil {
		log.Printf("Error getting user goals: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		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(ErrInvalidUserID+": %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": ErrInvalidUserID})
		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": ErrUserNotFound})
		} 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)
}

func deleteUserGoal(c *gin.Context) {
	userIdStr := c.Param("userId")
	goalIdStr := c.Param("goalId")

	userId, err := strconv.Atoi(userIdStr)
	if err != nil {
		log.Printf(ErrInvalidUserID+": %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": ErrInvalidUserID})
		return
	}

	goalId, err := strconv.Atoi(goalIdStr)
	if err != nil {
		log.Printf("Invalid goal ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
		return
	}

	exists, err := db.UserExists(userId)
	if err != nil {
		log.Printf(ErrCheckingUserExist, err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}
	if !exists {
		c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound})
		return
	}

	err = db.DeleteGoal(userId, goalId)
	if err != nil {
		if err.Error() == "goal not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
		} else {
			log.Printf("Error deleting goal: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		}
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"})
}

/*
*
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)
	router.DELETE("/api/user/:userId/goal/:goalId", deleteUserGoal)

	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)
}