package main

import (
	"embed"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/joho/godotenv"
	_ "github.com/lib/pq"
	"holiday-api/holiday"
	"holiday-api/migration"
	"html/template"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

//go:embed db/dev/*.sql
var devMigrations embed.FS

//go:embed db/prod/*.sql
var prodMigrations embed.FS

var isDev = false

func init() {
	godotenv.Load()
	if strings.Contains(os.Getenv("PROFILE"), "dev") {
		isDev = true
	}
	log.SetPrefix("")
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

func main() {
	client, err := connectToDb()
	if err != nil {
		log.Fatalf("couldn't connect to db: %v", err)
	}
	migrationFolder := prodMigrations
	if isDev {
		migrationFolder = devMigrations
	}
	if err := migration.InitializeMigrations(client, migrationFolder); err != nil {
		log.Fatalf("couldn't execute migrations: %v", err)
	}

	g := gin.Default()

	g.Static("assets", "assets")

	loadTemplates(g)

	holidayService := holiday.HolidayService{DB: client}
	countryService := holiday.CountryService{DB: client}
	yearService := holiday.YearService{DB: client}

	g.GET("/api/v1/holidays", getHolidays(holidayService))

	setupAdminDashboard(g.Group("/admin"), holidayService, countryService, yearService)

	g.GET("/", func(c *gin.Context) {
		search := holiday.Search{Country: "HR", Year: nil}
		if err := c.ShouldBindQuery(&search); err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}
		years, _ := yearService.Find()
		if search.Year == nil {
			c.HTML(http.StatusOK, "index.gohtml", gin.H{"Year": time.Now().Year(), "Years": years})
			return
		}
		countries, _ := countryService.Find()
		holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
		c.HTML(http.StatusOK, "results.gohtml", gin.H{"Years": years, "Countries": countries, "Search": search, "Holidays": mapHolidays(holidays).Holidays})
	})
	g.GET("/documentation", func(c *gin.Context) {
		countries, _ := countryService.Find()
		years, _ := yearService.Find()
		c.HTML(http.StatusOK, "documentation.gohtml", gin.H{"Year": time.Now().Year(), "Years": years, "Countries": countries})
	})
	log.Fatal(http.ListenAndServe(":5281", g))
}

func loadTemplates(g *gin.Engine) {
	g.SetFuncMap(template.FuncMap{
		"boolcmp": func(value *bool, expected string) bool {
			if value == nil {
				return expected == "nil"
			} else {
				return (*value && expected == "true") || (!(*value) && expected == "false")
			}
		},
		"deferint": func(value *int) int { return *value },
		"intpeq": func(selected *int, value int) bool {
			if selected != nil {
				return *selected == value
			}
			return false
		},
		"importSvg": IncludeHTML,
	})
	g.LoadHTMLFiles(
		"templates/index.gohtml",
		"templates/results.gohtml",
		"templates/documentation.gohtml",

		"templates/admin_dashboard.gohtml",
		"templates/countries.gohtml",

		"templates/dialogs/add-holiday.gohtml",
		"templates/dialogs/edit-holiday.gohtml",
		"templates/dialogs/delete-holiday.gohtml",

		"templates/dialogs/add-country.gohtml",
		"templates/dialogs/edit-country.gohtml",
		"templates/dialogs/delete-country.gohtml",
	)
}

func IncludeHTML(path string) template.HTML {
	b, err := os.ReadFile(path)
	if err != nil {
		log.Println("includeHTML - error reading file: %v", err)
		return ""
	}
	return template.HTML(string(b))
}

func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) {
	adminDashboard.Use(gin.BasicAuth(loadAuth()))

	adminDashboard.GET("/", func(c *gin.Context) {
		search := holiday.Search{Country: "HR", Year: new(int)}
		*search.Year = time.Now().Year()
		if err := c.ShouldBindQuery(&search); err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}
		holidays, _ := service.Find(search, holiday.Paging{PageSize: 100})
		holidayResponse := mapHolidays(holidays)
		countries, _ := countryService.Find()
		years, _ := yearService.Find()

		response := map[string]any{}
		response["Holidays"] = holidayResponse
		response["Search"] = search
		response["Countries"] = countries
		response["Years"] = years

		c.HTML(http.StatusOK, "admin_dashboard.gohtml", response)
	})
	adminDashboard.POST("/holidays", func(c *gin.Context) {
		request := struct {
			Id                 *string   `form:"id"`
			Name               string    `form:"name" binding:"required,min=1"`
			Description        string    `form:"description"`
			IsStateHoliday     bool      `form:"state_holiday"`
			IsReligiousHoliday bool      `form:"religious_holiday"`
			Country            string    `form:"country" binding:"len=2"`
			Date               time.Time `form:"date" time_format:"2006-01-02"`
		}{}
		if err := c.ShouldBind(&request); err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}

		hol := holiday.Holiday{
			Country:            request.Country,
			Date:               request.Date,
			Name:               request.Name,
			Description:        request.Description,
			IsStateHoliday:     request.IsStateHoliday,
			IsReligiousHoliday: request.IsReligiousHoliday,
		}

		var err error
		if request.Id != nil {
			hol.Id = uuid.MustParse(*request.Id)
			hol, err = service.Update(hol)
		} else {
			hol, err = service.Create(hol)
		}
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
		} else {
			c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.Date.Year()), 10))
		}
	})
	adminDashboard.POST("/holidays/:id/delete", func(c *gin.Context) {
		id := uuid.MustParse(c.Param("id"))
		hol, err := service.FindById(id)
		if err != nil {
			c.AbortWithError(http.StatusNotFound, err)
			return
		}
		if err := service.Delete(id); err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		c.Redirect(http.StatusSeeOther, "/admin?country="+hol.Country+"&year="+strconv.FormatInt(int64(hol.Date.Year()), 10))
	})
	adminDashboard.GET("/countries", func(c *gin.Context) {
		countries, _ := countryService.Find()
		c.HTML(http.StatusOK, "countries.gohtml", gin.H{"Countries": countries})
	})
	adminDashboard.POST("/countries", func(c *gin.Context) {
		request := struct {
			Id      *string `form:"id"`
			IsoName string  `form:"iso_name" binding:"required,min=2,max=2"`
			Name    string  `form:"name" binding:"required,min=1,max=45"`
		}{}
		if err := c.ShouldBind(&request); err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}

		country := holiday.Country{
			IsoName: request.IsoName,
			Name:    request.Name,
		}

		var err error
		if request.Id != nil {
			country.Id = uuid.MustParse(*request.Id)
			country, err = countryService.Update(country)
		} else {
			country, err = countryService.Create(country)
		}
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
		} else {
			c.Redirect(http.StatusSeeOther, "/admin/countries")
		}
	})
	adminDashboard.POST("/countries/:id/delete", func(c *gin.Context) {
		id := uuid.MustParse(c.Param("id"))
		_, err := countryService.FindById(id)
		if err != nil {
			c.AbortWithError(http.StatusNotFound, err)
			return
		}
		if err := countryService.Delete(id); err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		c.Redirect(http.StatusSeeOther, "/admin/countries")
	})
	adminDashboard.GET("/dialogs/add-country", func(c *gin.Context) {
		c.HTML(http.StatusOK, "add-country.gohtml", gin.H{})
	})

	adminDashboard.GET("/dialogs/add-holiday", func(c *gin.Context) {
		countries, _ := countryService.Find()
		c.HTML(http.StatusOK, "add-holiday.gohtml", gin.H{"Countries": countries})
	})
	adminDashboard.GET("/dialogs/edit-holiday", func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		hol, err := service.FindById(id)
		if err != nil {
			c.AbortWithError(http.StatusNotFound, err)
			return
		}
		countries, _ := countryService.Find()
		c.HTML(http.StatusOK, "edit-holiday.gohtml", gin.H{"Countries": countries, "Holiday": hol})
	})
	adminDashboard.GET("/dialogs/edit-country", func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		country, err := countryService.FindById(id)
		if err != nil {
			c.AbortWithError(http.StatusNotFound, err)
			return
		}
		c.HTML(http.StatusOK, "edit-country.gohtml", gin.H{"Country": country})
	})
	adminDashboard.GET("/dialogs/delete-holiday", func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		hol, err := service.FindById(id)
		if err != nil {
			c.AbortWithError(http.StatusNotFound, err)
			return
		}
		c.HTML(http.StatusOK, "delete-holiday.gohtml", gin.H{"Holiday": hol})
	})
	adminDashboard.GET("/dialogs/delete-country", func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		country, err := countryService.FindById(id)
		if err != nil {
			c.AbortWithError(http.StatusNotFound, err)
			return
		}
		c.HTML(http.StatusOK, "delete-country.gohtml", gin.H{"Country": country})
	})
}

func loadAuth() map[string]string {
	credentials := envMustExist("AUTH_KEY")
	values := strings.Split(credentials, ":")
	return map[string]string{values[0]: values[1]}
}
