package main

import (
	"context"
	"embed"
	"errors"
	"gitea.seeseepuff.be/seeseemelk/mysqlite"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"

	"github.com/gin-contrib/cors"
	"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.
	Addr string

	// The port that is actually being listened on.
	Port int

	// 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 errors.Is(err, mysqlite.ErrNoRows) {
		c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound})
		return
	}
	if err != nil {
		log.Printf("Error getting user: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	c.IndentedJSON(http.StatusOK, user)
}

func getUserAllowance(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
	}

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

func getUserAllowanceById(c *gin.Context) {
	userIdStr := c.Param("userId")
	allowanceIdStr := c.Param("allowanceId")

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

	allowanceId, err := strconv.Atoi(allowanceIdStr)
	if err != nil {
		log.Printf("Invalid allowance ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid allowance 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
	}

	allowance, err := db.GetUserAllowanceById(userId, allowanceId)
	if errors.Is(err, mysqlite.ErrNoRows) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Allowance not found"})
		return
	}
	if err != nil {
		log.Printf("Error getting allowance: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	c.IndentedJSON(http.StatusOK, allowance)
}

func createUserAllowance(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 CreateAllowanceRequest
	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": "Allowance name cannot be empty"})
		return
	}

	// Create goal in database
	goalId, err := db.CreateAllowance(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 bulkPutUserAllowance(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 {
		c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound})
		return
	}

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

	err = db.BulkUpdateAllowance(userId, allowanceRequest)
	if err != nil {
		log.Printf("Error updating allowance: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	c.IndentedJSON(http.StatusOK, gin.H{"message": "Allowance updated successfully"})
}

func deleteUserAllowance(c *gin.Context) {
	userIdStr := c.Param("userId")
	allowanceIdStr := c.Param("allowanceId")

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

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

	if allowanceId == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Allowance id zero cannot be deleted"})
		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.DeleteAllowance(userId, allowanceId)
	if err != nil {
		if err.Error() == "allowance not found" {
			c.JSON(http.StatusNotFound, gin.H{"error": "History not found"})
		} else {
			log.Printf("Error deleting allowance: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		}
		return
	}

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

func putUserAllowance(c *gin.Context) {
	userIdStr := c.Param("userId")
	allowanceIdStr := c.Param("allowanceId")

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

	allowanceId, err := strconv.Atoi(allowanceIdStr)
	if err != nil {
		log.Printf("Invalid allowance ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid allowance 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
	}

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

	if allowanceId == 0 {
		err = db.UpdateUserAllowance(userId, &allowanceRequest)
	} else {
		err = db.UpdateAllowance(userId, allowanceId, &allowanceRequest)
	}
	if err != nil {
		log.Printf("Error updating allowance: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	c.IndentedJSON(http.StatusOK, gin.H{"message": "Allowance updated successfully"})
}

func completeAllowance(c *gin.Context) {
	userIdStr := c.Param("userId")
	allowanceIdStr := c.Param("allowanceId")

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

	allowanceId, err := strconv.Atoi(allowanceIdStr)
	if err != nil {
		log.Printf("Invalid allowance ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid allowance 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.CompleteAllowance(userId, allowanceId)
	if errors.Is(err, mysqlite.ErrNoRows) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Allowance not found"})
		return
	}
	if err != nil {
		log.Printf("Error completing allowance: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	c.IndentedJSON(http.StatusOK, gin.H{"message": "Allowance completed successfully"})
}

func createTask(c *gin.Context) {
	var taskRequest CreateTaskRequest
	if err := c.ShouldBindJSON(&taskRequest); err != nil {
		log.Printf("Error parsing request body: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	if taskRequest.Name == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Task name cannot be empty"})
		return
	}

	// If assigned is not nil, check if user exists
	if taskRequest.Assigned != nil {
		exists, err := db.UserExists(*taskRequest.Assigned)
		if err != nil {
			log.Printf("Error checking user existence: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
			return
		}
		if !exists {
			c.JSON(http.StatusNotFound, gin.H{"error": ErrUserNotFound})
			return
		}
	}

	taskId, err := db.CreateTask(&taskRequest)
	if err != nil {
		log.Printf("Error creating task: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create task"})
		return
	}

	response := CreateTaskResponse{ID: taskId}
	c.IndentedJSON(http.StatusCreated, response)
}

func getTasks(c *gin.Context) {
	response, err := db.GetTasks()
	if err != nil {
		log.Printf("Error getting tasks: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}
	c.IndentedJSON(http.StatusOK, &response)
}

func getTask(c *gin.Context) {
	taskIdStr := c.Param("taskId")
	taskId, err := strconv.Atoi(taskIdStr)
	if err != nil {
		log.Printf("Invalid task ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid task ID"})
		return
	}
	response, err := db.GetTask(taskId)
	if errors.Is(err, mysqlite.ErrNoRows) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
		return
	}
	if err != nil {
		log.Printf("Error getting task: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}
	c.JSON(http.StatusOK, &response)
}

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

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

	_, err = db.GetTask(taskId)
	if errors.Is(err, mysqlite.ErrNoRows) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
		return
	}

	err = db.UpdateTask(taskId, &taskRequest)
	if err != nil {
		log.Printf("Error updating task: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

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

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

	hasTask, err := db.HasTask(taskId)
	if err != nil {
		log.Printf("Error checking task existence: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	if !hasTask {
		c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
		return
	}

	err = db.DeleteTask(taskId)
	if err != nil {
		log.Printf("Error deleting task: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

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

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

	err = db.CompleteTask(taskId)
	if errors.Is(err, mysqlite.ErrNoRows) {
		c.JSON(http.StatusNotFound, gin.H{"error": "Task not found"})
		return
	}
	if err != nil {
		log.Printf("Error completing task: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

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

func postHistory(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
	}

	var historyRequest PostHistory
	if err := c.ShouldBindJSON(&historyRequest); err != nil {
		log.Printf("Error parsing request body: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		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.AddHistory(userId, &historyRequest)
	if err != nil {
		log.Printf("Error updating history: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}
	c.JSON(http.StatusOK, gin.H{"message": "History updated successfully"})
}

func getHistory(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
	}

	history, err := db.GetHistory(userId)
	if err != nil {
		log.Printf("Error getting history: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": ErrInternalServerError})
		return
	}

	c.IndentedJSON(http.StatusOK, history)
}

/*
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()

	corsConfig := cors.DefaultConfig()
	corsConfig.AllowAllOrigins = true
	router.Use(cors.New(corsConfig))

	// Web endpoints
	loadWebEndpoints(router)
	// API endpoints
	router.GET("/api/users", getUsers)
	router.GET("/api/user/:userId", getUser)
	router.POST("/api/user/:userId/history", postHistory)
	router.GET("/api/user/:userId/history", getHistory)
	router.GET("/api/user/:userId/allowance", getUserAllowance)
	router.POST("/api/user/:userId/allowance", createUserAllowance)
	router.PUT("/api/user/:userId/allowance", bulkPutUserAllowance)
	router.GET("/api/user/:userId/allowance/:allowanceId", getUserAllowanceById)
	router.DELETE("/api/user/:userId/allowance/:allowanceId", deleteUserAllowance)
	router.PUT("/api/user/:userId/allowance/:allowanceId", putUserAllowance)
	router.POST("/api/user/:userId/allowance/:allowanceId/complete", completeAllowance)
	router.POST("/api/tasks", createTask)
	router.GET("/api/tasks", getTasks)
	router.GET("/api/task/:taskId", getTask)
	router.PUT("/api/task/:taskId", putTask)
	router.DELETE("/api/task/:taskId", deleteTask)
	router.POST("/api/task/:taskId/complete", completeTask)

	srv := &http.Server{
		Addr:    config.Addr,
		Handler: router.Handler(),
	}
	go func() {
		l, err := net.Listen("tcp", srv.Addr)
		if err != nil {
			log.Fatalf("listen: %s\n", err)
		}
		config.Port = l.Addr().(*net.TCPAddr).Port

		log.Printf("Running server on port %s\n", l.Addr().String())
		if config.Started != nil {
			config.Started <- true
		}

		if err := http.Serve(l, router.Handler()); err != nil {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	<-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"),
		Addr:       ":8080",
	}
	if config.Datasource == "" {
		config.Datasource = "allowance_planner.db3"
		log.Printf("Warning: No DB_PATH set, using default of %s", config.Datasource)
	}
	start(context.Background(), &config)
}