package holiday

import (
	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
	"strconv"
	"strings"
	"time"
)

type HolidayService struct {
	DB *sqlx.DB
}

func (s *HolidayService) Find(search Search, paging Paging) ([]Holiday, error) {
	var holidays []Holiday
	var err error
	if search.Date != nil {
		holidays, err = s.findByDate(*search.Date, search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
	} else if search.RangeStart != nil || search.RangeEnd != nil {
		holidays, err = s.findForRange(getDateOrDefault(search.RangeStart, 1000), getDateOrDefault(search.RangeEnd, 3000), search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
	} else if search.Year != nil {
		holidays, err = s.findByYear(*search.Year, search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
	} else {
		holidays, err = s.find(search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
	}
	if err != nil {
		return nil, err
	}
	return s.paginate(holidays, paging), err
}

func (s *HolidayService) FindById(id uuid.UUID) (Holiday, error) {
	var holiday Holiday
	return holiday, s.DB.Get(&holiday, `SELECT * FROM "holiday" WHERE "id" = $1;`, id)
}

func (s *HolidayService) findByDate(date time.Time, isState *bool, isReligious *bool, country string) ([]Holiday, error) {
	var holidays []Holiday
	return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE "date" = $1 AND country = $2 `+s.filter(isState, isReligious)+" ORDER BY \"date\";", date, country)
}

func (s *HolidayService) findForRange(rangeStart time.Time, rangeEnd time.Time, isState *bool, isReligious *bool, country string) ([]Holiday, error) {
	var holidays []Holiday
	return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE "date" BETWEEN $1 AND $2 AND country = $3`+s.filter(isState, isReligious)+" ORDER BY \"date\";", rangeStart, rangeEnd, country)
}

func (s *HolidayService) findByYear(year int, isState *bool, isReligious *bool, country string) ([]Holiday, error) {
	var holidays []Holiday
	return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE extract(year from "date") = $1 AND country = $2 `+s.filter(isState, isReligious)+" ORDER BY \"date\";", year, country)
}

func (s *HolidayService) find(isState *bool, isReligious *bool, country string) ([]Holiday, error) {
	var holidays []Holiday
	return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE country = $1 `+s.filter(isState, isReligious)+" ORDER BY \"date\";", country)
}

func (s *HolidayService) paginate(holidays []Holiday, paging Paging) []Holiday {
	start := paging.Page * paging.PageSize
	end := (paging.Page + 1) * paging.PageSize
	if end < len(holidays) {
		return holidays[start:end]
	} else if start < len(holidays) {
		return holidays[start:]
	} else {
		return []Holiday{}
	}
}

func (s *HolidayService) filter(isState *bool, isReligious *bool) string {
	var filters []string
	if isState != nil {
		filters = append(filters, "is_state = "+strconv.FormatBool(*isState))
	}
	if isReligious != nil {
		filters = append(filters, "is_religious = "+strconv.FormatBool(*isReligious))
	}
	if len(filters) > 0 {
		return " AND " + strings.Join(filters, " AND ")
	} else {
		return ""
	}
}

func (s *HolidayService) Update(holiday Holiday) (Holiday, error) {
	_, err := s.DB.Exec(`UPDATE holiday SET "country" = $1, "name" = $2, "description" = $3, "date" = $4, "is_state"=$5, "is_religious"=$6 WHERE "id" = $7`,
		&holiday.Country, &holiday.Name, &holiday.Description, &holiday.Date, &holiday.IsStateHoliday, &holiday.IsReligiousHoliday, &holiday.Id,
	)
	return holiday, err
}

func (s *HolidayService) Create(holiday Holiday) (Holiday, error) {
	holiday.Id = uuid.Must(uuid.NewRandom())
	_, err := s.DB.Exec(`INSERT INTO holiday (id, country, name, description, date, is_state, is_religious) values ($1, $2, $3, $4, $5, $6, $7)`,
		&holiday.Id, &holiday.Country, &holiday.Name, &holiday.Description, &holiday.Date, &holiday.IsStateHoliday, &holiday.IsReligiousHoliday,
	)
	return holiday, err
}

func (s *HolidayService) Delete(id uuid.UUID) error {
	_, err := s.DB.Exec(`DELETE FROM holiday WHERE "id" = $1`, &id)
	return err
}

func (s *HolidayService) Copy(country string, from int, to int) error {
	holidays, err := s.findByYear(from, nil, nil, country)
	if err != nil {
		return err
	}
	var diff = to - from

	for _, holiday := range holidays {
		holiday.Date = holiday.Date.AddDate(diff, 0, 0)
		_, err := s.Create(holiday)
		if err != nil {
			return err
		}
	}
	return nil
}

func getDateOrDefault(date *time.Time, year int) time.Time {
	if date == nil {
		return time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
	} else {
		return *date
	}
}
