package main

import (
	"errors"
	"github.com/gin-gonic/gin"
	"net/http"
	"strconv"
)

type ViewModel struct {
	Users       []User
	CurrentUser int
	Allowances  []Allowance
	Tasks       []Task
	History     []History
	Error       string
}

func loadWebEndpoints(router *gin.Engine) {
	router.LoadHTMLFiles("web.gohtml")
	router.GET("/", renderIndex)
	router.GET("/login", renderLogin)
	router.POST("/createTask", renderCreateTask)
	router.GET("/completeTask", renderCompleteTask)
	router.POST("/createAllowance", renderCreateAllowance)
	router.GET("/completeAllowance", renderCompleteAllowance)
}

func renderLogin(c *gin.Context) {
	if c.Query("user") != "" {
		c.SetCookie("user", c.Query("user"), 3600, "/", "localhost", false, true)
	}
	c.Redirect(http.StatusFound, "/")
}

func renderIndex(c *gin.Context) {
	currentUser := getCurrentUser(c)
	if currentUser == nil {
		return
	}
	renderWithUser(c, *currentUser)
}

func renderCreateTask(c *gin.Context) {
	currentUser := getCurrentUser(c)
	if currentUser == nil {
		return
	}

	name := c.PostForm("name")
	rewardStr := c.PostForm("reward")
	reward, err := strconv.ParseFloat(rewardStr, 64)
	if err != nil {
		renderError(c, http.StatusBadRequest, err)
		return
	}
	if name == "" || reward <= 0 {
		renderError(c, http.StatusBadRequest, err)
		return
	}

	_, err = db.CreateTask(&CreateTaskRequest{
		Name:   name,
		Reward: reward,
	})
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	c.Redirect(http.StatusFound, "/")
}

func renderCompleteTask(c *gin.Context) {
	taskIDStr := c.Query("task")
	taskID, err := strconv.Atoi(taskIDStr)
	if err != nil {
		renderError(c, http.StatusBadRequest, err)
		return
	}

	err = db.CompleteTask(taskID)
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	c.Redirect(http.StatusFound, "/")
}

func renderCreateAllowance(c *gin.Context) {
	currentUser := getCurrentUser(c)
	if currentUser == nil {
		return
	}

	name := c.PostForm("name")
	targetStr := c.PostForm("target")
	target, err := strconv.ParseFloat(targetStr, 64)
	if err != nil {
		renderError(c, http.StatusBadRequest, err)
		return
	}
	weightStr := c.PostForm("weight")
	weight, err := strconv.ParseFloat(weightStr, 64)
	if err != nil {
		renderError(c, http.StatusBadRequest, err)
		return
	}
	if name == "" || target <= 0 || weight <= 0 {
		renderError(c, http.StatusBadRequest, err)
		return
	}

	_, err = db.CreateAllowance(*currentUser, &CreateAllowanceRequest{
		Name:   name,
		Target: target,
		Weight: weight,
	})
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	c.Redirect(http.StatusFound, "/")
}

func renderCompleteAllowance(c *gin.Context) {
	currentUser := getCurrentUser(c)
	if currentUser == nil {
		return
	}

	allowanceIDStr := c.Query("allowance")
	allowanceID, err := strconv.Atoi(allowanceIDStr)
	if err != nil {
		renderError(c, http.StatusBadRequest, err)
		return
	}

	err = db.CompleteAllowance(*currentUser, allowanceID)
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	c.Redirect(http.StatusFound, "/")
}

func getCurrentUser(c *gin.Context) *int {
	currentUserStr, err := c.Cookie("user")
	if errors.Is(err, http.ErrNoCookie) {
		renderNoUser(c)
		return nil
	}
	if err != nil {
		unsetUserCookie(c)
		return nil
	}
	currentUser, err := strconv.Atoi(currentUserStr)
	if err != nil {
		unsetUserCookie(c)
		return nil
	}
	userExists, err := db.UserExists(currentUser)
	if !userExists || err != nil {
		unsetUserCookie(c)
		return nil
	}
	return &currentUser
}

func unsetUserCookie(c *gin.Context) {
	c.SetCookie("user", "", -1, "/", "localhost", false, true)
	c.Redirect(http.StatusFound, "/")
}

func renderNoUser(c *gin.Context) {
	users, err := db.GetUsers()
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	c.HTML(http.StatusOK, "web.gohtml", ViewModel{
		Users: users,
	})
}

func renderWithUser(c *gin.Context, currentUser int) {
	users, err := db.GetUsers()
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	allowances, err := db.GetUserAllowances(currentUser)
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	tasks, err := db.GetTasks()
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	history, err := db.GetHistory(currentUser)
	if err != nil {
		renderError(c, http.StatusInternalServerError, err)
		return
	}

	c.HTML(http.StatusOK, "web.gohtml", ViewModel{
		Users:       users,
		CurrentUser: currentUser,
		Allowances:  allowances,
		Tasks:       tasks,
		History:     history,
	})
}

func renderError(c *gin.Context, statusCode int, err error) {
	c.HTML(statusCode, "web.gohtml", ViewModel{
		Error: err.Error(),
	})
}