package mqe

import (
	"fmt"
	"time"

	"gordenko.dev/dima/qb/timeutil"
)

type PeriodCalculatorOptions struct {
	GroupBy        GroupBy
	LastDayOfMonth int
	FirstHourOfDay int
	Since          time.Time
	Until          time.Time
}

func NewPeriodCalculator(opt PeriodCalculatorOptions) PeriodCalculator {
	switch opt.GroupBy {
	case ByHour, ByDay, ByMonth:
		// pass

	default:
		panic(fmt.Sprintf("bug: unknown groupBy %q", opt.GroupBy))
	}

	s := PeriodCalculator{
		groupBy:        opt.GroupBy,
		lastDayOfMonth: opt.LastDayOfMonth,
		firstHourOfDay: opt.FirstHourOfDay,
	}
	s.sincePeriod = s.TimeToPeriod(opt.Since)
	s.untilPeriod = s.TimeToPeriod(opt.Until)

	return s
}

type PeriodCalculator struct {
	groupBy        GroupBy
	lastDayOfMonth int
	firstHourOfDay int
	sincePeriod    time.Time
	untilPeriod    time.Time
}

func (s PeriodCalculator) IsExtendedSincePeriod(period time.Time) bool {
	return period.Before(s.sincePeriod)
}

func (s PeriodCalculator) IsExtendedUntilPeriod(period time.Time) bool {
	return period.After(s.untilPeriod)
}

func (s PeriodCalculator) NextPeriod(period time.Time) time.Time {
	switch s.groupBy {
	case ByHour:
		return period.Add(time.Hour)

	case ByDay:
		return period.AddDate(0, 0, 1)

	case ByMonth:
		return period.AddDate(0, 1, 0)

	default:
		panic(fmt.Sprintf("bug: unknown groupBy %q", s.groupBy))
	}
}

func (s PeriodCalculator) IsPeriodCorrectEnds(period time.Time, lastMeasureTime int64) bool {
	// по умолчанию
	tm := timeutil.LastSecondInPeriod(period, string(s.groupBy))

	switch s.groupBy {
	case ByDay:
		if s.firstHourOfDay > 0 {
			// Было 23:59, стало - 01:59
			tm = tm.Add(time.Duration(s.firstHourOfDay) * time.Hour)
		}

	case ByMonth:
		if s.lastDayOfMonth == 0 {
			if s.firstHourOfDay > 0 {
				// Было 23:59, стало - 01:59
				tm = tm.Add(time.Duration(s.firstHourOfDay) * time.Hour)
			}
		} else {
			if s.firstHourOfDay > 0 {
				// last day 28, first hour 2 превращается в day 29 01:59:59
				tm = time.Date(
					tm.Year(),
					tm.Month(),
					s.lastDayOfMonth+1,
					s.firstHourOfDay-1,
					59,
					59,
					0,
					tm.Location(),
				)
			} else {
				// last day 23:59:59
				tm = time.Date(
					tm.Year(),
					tm.Month(),
					s.lastDayOfMonth,
					23,
					59,
					59,
					0,
					tm.Location(),
				)
			}
		}
	}

	timestamp := tm.Add(-179 * time.Second).Unix()

	if lastMeasureTime >= timestamp && lastMeasureTime <= tm.Unix() {
		return true
	}
	//fmt.Printf("Ends: lastMeasureTime=%s, timetsamp=%s\n", time.Unix(lastMeasureTime, 0), time.Unix(timestamp, 0))
	return false
}

func (s PeriodCalculator) TimeToPeriod(tm time.Time) (period time.Time) {
	switch s.groupBy {
	case ByDay:
		if s.firstHourOfDay > 0 {
			if tm.Hour() < s.firstHourOfDay {
				tm = tm.AddDate(0, 0, -1)
			}
		}

	case ByMonth:
		if s.lastDayOfMonth > 0 {
			if s.lastDayOfMonth > 28 {
				// На всякий случай добавляем защиту от дурака, ибо модификация даты
				// в запросе (+1 месяц) для 29, 30 и 31 чисел сделает запрос неверным.
				panic(fmt.Sprintf("LastDayOfMonth: %d, max allowed value is 28", s.lastDayOfMonth))
			}

			if s.firstHourOfDay > 0 {
				if tm.Hour() >= s.firstHourOfDay {
					if tm.Day() > s.lastDayOfMonth {
						// add 1 month
						tm = tm.AddDate(0, 1, 0)
					}
				} else {
					// firstHourOfDay показывает что текущий день еще не наступил,
					// поэтому day - 1
					if (tm.Day() - 1) <= s.lastDayOfMonth {
						tm = tm.AddDate(0, 0, -1)
					} else {
						// - 1 day, + 1 month
						tm = tm.AddDate(0, 0, -1).AddDate(0, 1, 0)
					}
				}
			} else {
				if tm.Day() > s.lastDayOfMonth {
					tm = tm.AddDate(0, 1, 0)
				}
			}
		} else {
			if s.firstHourOfDay > 0 {
				if tm.Hour() < s.firstHourOfDay {
					tm = tm.AddDate(0, 0, -1)
				}
			}
		}
	}
	return timeutil.FirstSecondInPeriod(tm, string(s.groupBy))
}
