package api

import (
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"holiday-api/domain/holiday"
	"log/slog"
	"net/http"
	"strconv"
	"time"
)

func NoMethod() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.AbortWithStatus(http.StatusNotFound)
	}
}

func NoRoute() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.AbortWithStatus(http.StatusNotFound)
	}
}

func GetIndex(holidayService holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) gin.HandlerFunc {
	return 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": holiday.ToResponse(holidays).Holidays})
	}
}

func GetDocumentation(countryService holiday.CountryService, yearService holiday.YearService) gin.HandlerFunc {
	return 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})
	}
}

func GetHolidays(service holiday.HolidayService) gin.HandlerFunc {
	return func(c *gin.Context) {
		paging := holiday.Paging{PageSize: 50}
		if err := c.ShouldBindQuery(&paging); err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}

		search := holiday.Search{}
		if err := c.ShouldBindQuery(&search); err != nil {
			c.AbortWithError(http.StatusBadRequest, err)
			return
		}

		holidays, err := service.Find(search, paging)
		if err != nil {
			render(c, http.StatusNotFound, holiday.ErrorResponse{Created: time.Now(), Message: "failed fetching holidays"}, nil)
		} else {
			render(c, http.StatusOK, holiday.ToResponse(holidays), search.Type)
		}
	}
}

func DeleteCountry(countryService holiday.CountryService) gin.HandlerFunc {
	return func(c *gin.Context) {
		id := uuid.MustParse(c.Param("id"))
		_, err := countryService.FindById(id)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't find country")
			return
		}
		if err := countryService.Delete(id); err != nil {
			Abort(c, err, http.StatusInternalServerError, "couldn't delete country")
			return
		}
		c.Redirect(http.StatusSeeOther, "/admin/countries")
	}
}

func CreateOrUpdateCountry(countryService holiday.CountryService) gin.HandlerFunc {
	return 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 {
			Abort(c, err, http.StatusInternalServerError, "invalid request when creating or updating country")
			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 {
			Abort(c, err, http.StatusInternalServerError, "couldn't create or update country")
		} else {
			c.Redirect(http.StatusSeeOther, "/admin/countries")
		}
	}
}

func GetCountries(countryService holiday.CountryService) gin.HandlerFunc {
	return func(c *gin.Context) {
		countries, _ := countryService.Find()
		c.HTML(http.StatusOK, "countries.gohtml", gin.H{"Countries": countries})
	}
}

func DeleteHoliday(holidayService holiday.HolidayService) gin.HandlerFunc {
	return func(c *gin.Context) {
		id := uuid.MustParse(c.Param("id"))
		hol, err := holidayService.FindById(id)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't find holiday to delete")
			return
		}
		if err := holidayService.Delete(id); err != nil {
			Abort(c, err, http.StatusInternalServerError, "couldn't delete holiday")
			return
		}
		c.Redirect(http.StatusSeeOther, "/admin?country="+hol.Country+"&year="+strconv.FormatInt(int64(hol.Date.Year()), 10))
	}
}

func CopyYear(holidayService holiday.HolidayService) gin.HandlerFunc {
	return func(c *gin.Context) {
		request := struct {
			From    int    `form:"from"`
			To      int    `form:"to"`
			Country string `form:"country" binding:"len=2"`
		}{}
		if err := c.ShouldBind(&request); err != nil {
			Abort(c, err, http.StatusBadRequest, "invalid request when copying holiday")
			return
		}

		err := holidayService.Copy(request.Country, request.From, request.To)
		if err != nil {
			Abort(c, err, http.StatusInternalServerError, "couldn't copy holidays")
		} else {
			c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.To), 10))
		}
	}
}

func CreateOrUpdateHoliday(holidayService holiday.HolidayService) gin.HandlerFunc {
	return 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 {
			Abort(c, err, http.StatusBadRequest, "invalid request when creating holiday")
			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 = holidayService.Update(hol)
		} else {
			hol, err = holidayService.Create(hol)
		}
		if err != nil {
			Abort(c, err, 500, "couldn't create or update holiday")
		} else {
			c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.Date.Year()), 10))
		}
	}
}

func GetAdminHome(holidayService holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) gin.HandlerFunc {
	return func(c *gin.Context) {
		search := holiday.Search{Country: "HR", Year: new(int)}
		*search.Year = time.Now().Year()
		if err := c.ShouldBindQuery(&search); err != nil {
			Abort(c, err, http.StatusBadRequest, "invalid search parameters")
			return
		}
		holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
		holidayResponse := holiday.ToResponse(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)
	}
}

func AddHolidayDialog(countryService holiday.CountryService) gin.HandlerFunc {
	return func(c *gin.Context) {
		countries, _ := countryService.Find()
		c.HTML(http.StatusOK, "add-holiday.gohtml", gin.H{"Countries": countries})
	}
}

func EditHolidayDialog(holidayService holiday.HolidayService, countryService holiday.CountryService) gin.HandlerFunc {
	return func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		hol, err := holidayService.FindById(id)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't find holiday to edit")
			return
		}
		countries, _ := countryService.Find()
		c.HTML(http.StatusOK, "edit-holiday.gohtml", gin.H{"Countries": countries, "Holiday": hol})
	}
}

func DeleteHolidayDialog(holidayService holiday.HolidayService) gin.HandlerFunc {
	return func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		hol, err := holidayService.FindById(id)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't find dialog")
			return
		}
		c.HTML(http.StatusOK, "delete-holiday.gohtml", gin.H{"Holiday": hol})
	}
}

func CopyYearDialog() gin.HandlerFunc {
	return func(c *gin.Context) {
		country := c.Query("country")
		year, err := strconv.ParseInt(c.Query("year"), 10, 32)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't parse year")
			return
		}
		c.HTML(http.StatusOK, "copy-year.gohtml", gin.H{"Country": country, "Year": year})
	}
}

func AddCountryDialog() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.HTML(http.StatusOK, "add-country.gohtml", gin.H{})
	}
}

func EditCountryDialog(countryService holiday.CountryService) gin.HandlerFunc {
	return func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		country, err := countryService.FindById(id)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't find country")
			return
		}
		c.HTML(http.StatusOK, "edit-country.gohtml", gin.H{"Country": country})
	}
}

func DeleteCountryDialog(countryService holiday.CountryService) gin.HandlerFunc {
	return func(c *gin.Context) {
		id := uuid.MustParse(c.Query("id"))
		country, err := countryService.FindById(id)
		if err != nil {
			Abort(c, err, http.StatusNotFound, "couldn't find dialog")
			return
		}
		c.HTML(http.StatusOK, "delete-country.gohtml", gin.H{"Country": country})
	}
}

type CSV interface {
	CSV() []byte
}

func render(c *gin.Context, status int, response any, contentType *string) {
	value := c.GetHeader("accept")
	if contentType != nil {
		switch *contentType {
		case "xml":
			value = "text/xml"
		case "json":
			value = "application/json"
		case "csv":
			value = "text/csv"
		}
	}
	switch value {
	case "text/csv":
		if csvResponse, ok := response.(CSV); ok {
			c.Data(200, value+"; charset=utf-8", csvResponse.CSV())
		} else {
			c.Header("content-type", "application/json; charset=utf-8")
			c.JSON(status, response)
		}
	case "application/xml":
		fallthrough
	case "text/xml":
		c.Header("content-type", value+"; charset=utf-8; header=present;")
		c.XML(status, response)
	case "application/json":
		fallthrough
	default:
		c.Header("content-type", "application/json; charset=utf-8")
		c.JSON(status, response)
	}
}

func Abort(c *gin.Context, err error, statusCode int, message string) {
	slog.Error(message, slog.String("err", errorMessage(err)))
	c.AbortWithError(statusCode, err)
}

func errorMessage(err error) string {
	if err != nil {
		return err.Error()
	}
	return "-"
}
