package db_utils

import (
	"fmt"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/date_utils"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/number_utils"
	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/struct_utils"
	"regexp"
	"strings"
	"time"
)

// Special query cases
const (
	LastXHours string = "^last \\d+ hour(s)?$"
	LastDay    string = "^last day$"
	LastXDays  string = "^last \\d+ day(s)?$"
	LastMonth  string = "^last month$"
	Yesterday  string = "^yesterday$"
)

func checkAbsoluteRegex(regex string, value string) bool {
	matchesRegex, err := regexp.Match(regex, []byte(value))
	if matchesRegex && err == nil {
		return true
	}

	return false
}

func pullAbsoluteValue(value string) int64 {
	reg, err := regexp.Compile("[a-zA-Z]+")
	if err != nil {
		return 0
	}
	value = reg.ReplaceAllString(value, "")
	value = strings.TrimSpace(value)

	val, _ := number_utils.StringToInt64(value)
	return val
}

func AbsoluteDateStringQuery(absolute string) string {
	var value int64
	timeNow := date_utils.CurrentDate()

	if checkAbsoluteRegex(LastXHours, absolute) {
		value = pullAbsoluteValue(absolute)
		since := timeNow.Add(time.Duration(-value) * time.Hour)

		return fmt.Sprintf(" >= '%v'", date_utils.DateDBFormattedString(since))
	}
	if checkAbsoluteRegex(LastDay, absolute) {
		startDay := time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 0, 0, 0, 0, date_utils.CurrentLocation())
		endDay := time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 23, 59, 59, 999999999, date_utils.CurrentLocation())

		return fmt.Sprintf(" BETWEEN '%v' AND '%v'", date_utils.DateDBFormattedString(startDay), date_utils.DateDBFormattedString(endDay))
	}
	if checkAbsoluteRegex(LastXDays, absolute) {
		value = pullAbsoluteValue(absolute)
		since := timeNow.AddDate(0, 0, -int(value))

		return fmt.Sprintf(" >= '%v'", date_utils.DateDBFormattedString(since))
	}
	if checkAbsoluteRegex(LastMonth, absolute) {
		firstOfMonth := time.Date(timeNow.Year(), timeNow.Month(), 1, 0, 0, 0, 0, date_utils.CurrentLocation())
		lastDay := firstOfMonth.AddDate(0, 1, -1)
		lastOfMonth := time.Date(lastDay.Year(), lastDay.Month(), lastDay.Day(), 23, 59, 59, 999999999, date_utils.CurrentLocation())

		return fmt.Sprintf(" BETWEEN '%v' AND '%v'", date_utils.DateDBFormattedString(firstOfMonth), date_utils.DateDBFormattedString(lastOfMonth))
	}
	if checkAbsoluteRegex(Yesterday, absolute) {
		yesterday := timeNow.AddDate(0, 0, -1)
		startDay := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, date_utils.CurrentLocation())
		endDay := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 23, 59, 59, 999999999, date_utils.CurrentLocation())

		return fmt.Sprintf(" BETWEEN '%v' AND '%v'", date_utils.DateDBFormattedString(startDay), date_utils.DateDBFormattedString(endDay))
	}

	// Not matched, so try to match it literally (It'll probably break and return nothing, but that's good)
	return fmt.Sprintf(" = '%v'", absolute)
}

type PageAndOrderParams struct {
	PageParams
	OrderBy string `json:"order_by,omitempty"`
	Order   string `json:"order,omitempty"`
}

type PageParams struct {
	Limit  int64 `json:"limit,omitempty"`  // Limit number of items returned in list (default: 0 (no limit))
	Offset int64 `json:"offset,omitempty"` // Offset in list (default: 0 (no offset))
}

func (params *PageParams) Validate() error {
	if params.Limit < 0 {
		return errors.Errorf("limit=%d must be positive", params.Limit)
	}
	if params.Offset < 0 {
		return errors.Errorf("offset=%d must be >=0", params.Offset)
	}
	return nil
}

func (params *PageParams) EnforceLimit() {
	if params.Limit <= 0 {
		params.Limit = 20
	}
}

func ValidateDateFilters(params any, dateFiltersToValidate ...string) error {
	queryParams := struct_utils.MapParams(params)
	for _, filter := range dateFiltersToValidate {
		date, present := queryParams[filter]
		if present {
			_, err := date_utils.ParseTimeString(date)
			if err != nil {
				return errors.Errorf("invalid %s: %s", filter, date)
			}
		}
	}
	return nil
}