package notifications

import (
	"slices"
	"time"
)

// GetScheduleForFrequency returns the reminder schedule (days before due date)
// based on the task's frequency interval days.
// Returns nil or 0 interval as Once schedule.
func GetScheduleForFrequency(intervalDays *int) []int {
	if intervalDays == nil || *intervalDays == 0 {
		return FrequencySchedules[0] // Once schedule
	}

	days := *intervalDays

	// Check for exact match first
	if schedule, exists := FrequencySchedules[days]; exists {
		return schedule
	}

	// For Custom frequencies, find the nearest tier
	return GetScheduleForCustomInterval(days)
}

// GetScheduleForCustomInterval looks up schedule for Custom frequency
// based on CustomIntervalDays value - finds nearest tier
func GetScheduleForCustomInterval(days int) []int {
	// Ordered tiers from smallest to largest
	tiers := []int{1, 7, 14, 30, 90, 180, 365}

	for _, tier := range tiers {
		if days <= tier {
			return FrequencySchedules[tier]
		}
	}

	// Fallback to annual schedule for intervals > 365 days
	return FrequencySchedules[365]
}

// ShouldSendOverdueReminder checks if we should send an overdue reminder
// for a task that is daysOverdue days past its due date.
//
// Returns true if:
// - Days 1 to DailyReminderDays: every day
// - Days DailyReminderDays+1 to MaxOverdueDays: every TaperIntervalDays
// - After MaxOverdueDays: never
func ShouldSendOverdueReminder(daysOverdue int) bool {
	if daysOverdue <= 0 {
		return false
	}

	// Phase 1: Daily reminders for first N days
	if daysOverdue <= OverdueConfig.DailyReminderDays {
		return true
	}

	// Phase 2: Tapered reminders (every N days)
	if daysOverdue <= OverdueConfig.MaxOverdueDays {
		// Calculate which day in the taper phase
		daysSinceDaily := daysOverdue - OverdueConfig.DailyReminderDays
		// Send on days that are multiples of TaperIntervalDays after daily phase
		// e.g., with DailyReminderDays=3, TaperIntervalDays=3:
		// Day 4 (daysSinceDaily=1): 1 % 3 = 1 (no)
		// Day 5 (daysSinceDaily=2): 2 % 3 = 2 (no)
		// Day 6 (daysSinceDaily=3): 3 % 3 = 0 (yes) -> but we want day 4, 7, 10, 13
		// Correcting: we want to send on daysSinceDaily = 1, 4, 7, 10...
		// So (daysSinceDaily - 1) % TaperIntervalDays == 0
		return (daysSinceDaily-1)%OverdueConfig.TaperIntervalDays == 0
	}

	// Phase 3: No more reminders
	return false
}

// GetOverdueReminderDays returns all the days on which overdue reminders
// should be sent, based on the current OverdueConfig.
func GetOverdueReminderDays() []int {
	var days []int

	// Add daily reminder days
	for i := 1; i <= OverdueConfig.DailyReminderDays; i++ {
		days = append(days, i)
	}

	// Add tapered reminder days
	for i := OverdueConfig.DailyReminderDays + 1; i <= OverdueConfig.MaxOverdueDays; i++ {
		if ShouldSendOverdueReminder(i) {
			days = append(days, i)
		}
	}

	return days
}

// GetReminderStageForToday determines which reminder stage applies today
// for a task with the given due date and frequency.
//
// Returns:
// - "reminder_Nd" if task is N days away and in schedule
// - "day_of" if task is due today
// - "overdue_N" if task is N days overdue and should be reminded
// - empty string if no reminder should be sent today
func GetReminderStageForToday(dueDate time.Time, frequencyDays *int, today time.Time) string {
	// Normalize to date only (midnight)
	dueDate = time.Date(dueDate.Year(), dueDate.Month(), dueDate.Day(), 0, 0, 0, 0, time.UTC)
	today = time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)

	// Calculate days difference
	diff := int(dueDate.Sub(today).Hours() / 24)

	if diff > 0 {
		// Task is in the future - check pre-due reminders
		schedule := GetScheduleForFrequency(frequencyDays)
		if slices.Contains(schedule, diff) {
			return formatDaysBeforeStage(diff)
		}
		return ""
	} else if diff == 0 {
		// Task is due today
		return "day_of"
	} else {
		// Task is overdue
		daysOverdue := -diff
		if ShouldSendOverdueReminder(daysOverdue) {
			return formatOverdueStage(daysOverdue)
		}
		return ""
	}
}

// formatDaysBeforeStage returns the stage string for days before due date
func formatDaysBeforeStage(days int) string {
	if days == 0 {
		return "day_of"
	}
	return "reminder_" + itoa(days) + "d"
}

// formatOverdueStage returns the stage string for days overdue
func formatOverdueStage(days int) string {
	return "overdue_" + itoa(days)
}

// itoa is a simple int to string helper
func itoa(i int) string {
	if i == 0 {
		return "0"
	}
	if i < 0 {
		return "-" + itoa(-i)
	}
	s := ""
	for i > 0 {
		s = string(rune('0'+i%10)) + s
		i /= 10
	}
	return s
}

// GetHumanReadableSchedule returns a human-readable description of the
// notification schedule for a given frequency interval.
func GetHumanReadableSchedule(intervalDays *int) string {
	days := 0
	if intervalDays != nil {
		days = *intervalDays
	}

	if desc, exists := HumanReadableSchedule[days]; exists {
		return desc
	}

	// For custom intervals, find the nearest tier
	tiers := []int{1, 7, 14, 30, 90, 180, 365}
	for _, tier := range tiers {
		if days <= tier {
			return HumanReadableSchedule[tier] + " (custom)"
		}
	}

	return HumanReadableSchedule[365] + " (custom)"
}

// GetFrequencyName returns the human-readable name for a frequency interval
func GetFrequencyName(intervalDays *int) string {
	days := 0
	if intervalDays != nil {
		days = *intervalDays
	}

	if name, exists := FrequencyNames[days]; exists {
		return name
	}

	return "Custom"
}
