package services

import (
	"fmt"
	"strings"
	"time"

	"github.com/jmoiron/sqlx"
)

// Holiday represents a non-working day
type Holiday struct {
	Date       time.Time
	Name       string
	IsVacation bool // Part of court vacation period
	IsClosure  bool // Single-day closure (public holiday)
}

// HolidayService manages holiday data and non-working day checks
type HolidayService struct {
	db *sqlx.DB
	// Cached holidays by year
	cache map[int][]Holiday
}

// NewHolidayService creates a holiday service
func NewHolidayService(db *sqlx.DB) *HolidayService {
	return &HolidayService{
		db:    db,
		cache: make(map[int][]Holiday),
	}
}

// dbHoliday matches the holidays table schema
type dbHoliday struct {
	ID          int       `db:"id"`
	Date        time.Time `db:"date"`
	Name        string    `db:"name"`
	Country     string    `db:"country"`
	State       *string   `db:"state"`
	HolidayType string    `db:"holiday_type"`
}

// LoadHolidaysForYear loads holidays from DB for a given year, merges with
// German federal holidays, and caches the result.
func (s *HolidayService) LoadHolidaysForYear(year int) ([]Holiday, error) {
	if cached, ok := s.cache[year]; ok {
		return cached, nil
	}

	holidays := make([]Holiday, 0, 30)

	// Load from DB if available
	if s.db != nil {
		var dbHolidays []dbHoliday
		err := s.db.Select(&dbHolidays,
			`SELECT id, date, name, country, state, holiday_type
			 FROM holidays
			 WHERE EXTRACT(YEAR FROM date) = $1
			 ORDER BY date`, year)
		if err == nil {
			for _, h := range dbHolidays {
				holidays = append(holidays, Holiday{
					Date:      h.Date,
					Name:      h.Name,
					IsClosure: h.HolidayType == "public_holiday" || h.HolidayType == "closure",
					IsVacation: h.HolidayType == "vacation",
				})
			}
		}
		// If DB query fails, fall through to hardcoded holidays
	}

	// Always add German federal holidays (if not already present from DB)
	federal := germanFederalHolidays(year)
	existing := make(map[string]bool, len(holidays))
	for _, h := range holidays {
		existing[h.Date.Format("2006-01-02")] = true
	}
	for _, h := range federal {
		key := h.Date.Format("2006-01-02")
		if !existing[key] {
			holidays = append(holidays, h)
		}
	}

	s.cache[year] = holidays
	return holidays, nil
}

// IsHoliday checks if a date is a holiday
func (s *HolidayService) IsHoliday(date time.Time) *Holiday {
	year := date.Year()
	holidays, err := s.LoadHolidaysForYear(year)
	if err != nil {
		return nil
	}

	dateStr := date.Format("2006-01-02")
	for i := range holidays {
		if holidays[i].Date.Format("2006-01-02") == dateStr {
			return &holidays[i]
		}
	}
	return nil
}

// IsNonWorkingDay returns true if the date is a weekend or holiday
func (s *HolidayService) IsNonWorkingDay(date time.Time) bool {
	wd := date.Weekday()
	if wd == time.Saturday || wd == time.Sunday {
		return true
	}
	return s.IsHoliday(date) != nil
}

// AdjustForNonWorkingDays moves the date to the next working day
// if it falls on a weekend or holiday.
// Returns adjusted date, original date, and whether adjustment was made.
func (s *HolidayService) AdjustForNonWorkingDays(date time.Time) (adjusted time.Time, original time.Time, wasAdjusted bool) {
	original = date
	adjusted = date

	// Safety limit: max 30 days forward
	for i := 0; i < 30 && s.IsNonWorkingDay(adjusted); i++ {
		adjusted = adjusted.AddDate(0, 0, 1)
		wasAdjusted = true
	}

	return adjusted, original, wasAdjusted
}

// ClearCache clears the holiday cache (useful after DB updates)
func (s *HolidayService) ClearCache() {
	s.cache = make(map[int][]Holiday)
}

// germanFederalHolidays returns all German federal public holidays for a year.
// These are holidays observed in all 16 German states.
func germanFederalHolidays(year int) []Holiday {
	easterMonth, easterDay := CalculateEasterSunday(year)
	easter := time.Date(year, time.Month(easterMonth), easterDay, 0, 0, 0, 0, time.UTC)

	holidays := []Holiday{
		{Date: time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC), Name: "Neujahr", IsClosure: true},
		{Date: easter.AddDate(0, 0, -2), Name: "Karfreitag", IsClosure: true},
		{Date: easter, Name: "Ostersonntag", IsClosure: true},
		{Date: easter.AddDate(0, 0, 1), Name: "Ostermontag", IsClosure: true},
		{Date: time.Date(year, time.May, 1, 0, 0, 0, 0, time.UTC), Name: "Tag der Arbeit", IsClosure: true},
		{Date: easter.AddDate(0, 0, 39), Name: "Christi Himmelfahrt", IsClosure: true},
		{Date: easter.AddDate(0, 0, 49), Name: "Pfingstsonntag", IsClosure: true},
		{Date: easter.AddDate(0, 0, 50), Name: "Pfingstmontag", IsClosure: true},
		{Date: time.Date(year, time.October, 3, 0, 0, 0, 0, time.UTC), Name: "Tag der Deutschen Einheit", IsClosure: true},
		{Date: time.Date(year, time.December, 25, 0, 0, 0, 0, time.UTC), Name: "1. Weihnachtstag", IsClosure: true},
		{Date: time.Date(year, time.December, 26, 0, 0, 0, 0, time.UTC), Name: "2. Weihnachtstag", IsClosure: true},
	}

	return holidays
}

// CalculateEasterSunday computes Easter Sunday using the Anonymous Gregorian algorithm.
// Returns month (1-12) and day.
func CalculateEasterSunday(year int) (int, int) {
	a := year % 19
	b := year / 100
	c := year % 100
	d := b / 4
	e := b % 4
	f := (b + 8) / 25
	g := (b - f + 1) / 3
	h := (19*a + b - d - g + 15) % 30
	i := c / 4
	k := c % 4
	l := (32 + 2*e + 2*i - h - k) % 7
	m := (a + 11*h + 22*l) / 451
	month := (h + l - 7*m + 114) / 31
	day := ((h + l - 7*m + 114) % 31) + 1
	return month, day
}

// GetHolidaysForYear returns all holidays for a year (for API exposure)
func (s *HolidayService) GetHolidaysForYear(year int) ([]Holiday, error) {
	return s.LoadHolidaysForYear(year)
}

// FormatHolidayList returns a simple string representation of holidays for debugging
func FormatHolidayList(holidays []Holiday) string {
	var b strings.Builder
	for _, h := range holidays {
		fmt.Fprintf(&b, "%s: %s\n", h.Date.Format("2006-01-02"), h.Name)
	}
	return b.String()
}
