package background

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

// ScheduleRule defines a time-based interval rule.
type ScheduleRule struct {
	Name            string   `json:"name"`
	Days            []string `json:"days"`
	TimeRange       string   `json:"time_range"`
	Except          []string `json:"except"`
	IntervalMinutes int      `json:"interval_minutes"`
}

// ThinkingScheduleConfig is the full schedule configuration.
type ThinkingScheduleConfig struct {
	Version               string         `json:"version"`
	DefaultIntervalMinutes int            `json:"default_interval_minutes"`
	Rules                 []ScheduleRule `json:"rules"`
}

// ScheduleLoader loads the thinking schedule from a JSON file and calculates
// the current interval based on time of day and day of week.
type ScheduleLoader struct {
	mu     sync.RWMutex
	path   string
	config *ThinkingScheduleConfig
}

// NewScheduleLoader creates a loader. Returns nil config if the file does not exist.
func NewScheduleLoader(path string) (*ScheduleLoader, error) {
	l := &ScheduleLoader{path: path}
	if err := l.load(); err != nil {
		return l, err
	}
	return l, nil
}

func (l *ScheduleLoader) load() error {
	data, err := os.ReadFile(l.path)
	if err != nil {
		if os.IsNotExist(err) {
			l.config = nil
			return nil
		}
		return fmt.Errorf("read thinking schedule: %w", err)
	}
	if len(data) == 0 {
		l.config = nil
		return nil
	}
	var cfg ThinkingScheduleConfig
	if err := json.Unmarshal(data, &cfg); err != nil {
		l.config = nil
		return fmt.Errorf("parse thinking schedule: %w", err)
	}
	l.mu.Lock()
	l.config = &cfg
	l.mu.Unlock()
	log.Printf("[思考调度] 已加载配置文件: version=%s, default=%dmin, rules=%d", cfg.Version, cfg.DefaultIntervalMinutes, len(cfg.Rules))
	return nil
}

// HasConfig returns true if a schedule config was loaded from file.
func (l *ScheduleLoader) HasConfig() bool {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.config != nil
}

// GetInterval returns the thinking interval in minutes for the given time.
// Returns 0 if no schedule is loaded (caller should use default).
func (l *ScheduleLoader) GetInterval(now time.Time) int {
	l.mu.RLock()
	defer l.mu.RUnlock()

	if l.config == nil {
		return 0
	}

	weekday := strings.ToLower(now.Weekday().String()) // monday, tuesday, ...
	currentMinutes := now.Hour()*60 + now.Minute()

	for _, rule := range l.config.Rules {
		if !matchDay(weekday, rule.Days) {
			continue
		}
		if !matchTimeRange(currentMinutes, rule.TimeRange) {
			continue
		}
		if matchExceptRange(currentMinutes, rule.Except) {
			continue
		}
		return rule.IntervalMinutes
	}

	return l.config.DefaultIntervalMinutes
}

// matchDay checks if the current weekday is in the rule's days list.
func matchDay(currentDay string, days []string) bool {
	for _, d := range days {
		if strings.ToLower(d) == currentDay {
			return true
		}
	}
	return false
}

// matchTimeRange checks if currentMinutes (0-1439) falls within the time range.
// Supports overnight ranges (e.g., 23:00-07:00 where start > end).
func matchTimeRange(currentMinutes int, timeRange string) bool {
	start, end, ok := parseTimeRange(timeRange)
	if !ok {
		return false
	}
	if start <= end {
		return currentMinutes >= start && currentMinutes < end
	}
	// Overnight range
	return currentMinutes >= start || currentMinutes < end
}

// matchExceptRange returns true if currentMinutes falls in any except range.
func matchExceptRange(currentMinutes int, exceptRanges []string) bool {
	for _, er := range exceptRanges {
		start, end, ok := parseTimeRange(er)
		if !ok {
			continue
		}
		if start <= end {
			if currentMinutes >= start && currentMinutes < end {
				return true
			}
		} else {
			if currentMinutes >= start || currentMinutes < end {
				return true
			}
		}
	}
	return false
}

// parseTimeRange parses "HH:MM-HH:MM" into start and end minutes from midnight.
func parseTimeRange(r string) (int, int, bool) {
	parts := strings.SplitN(r, "-", 2)
	if len(parts) != 2 {
		return 0, 0, false
	}
	start, ok := parseHM(strings.TrimSpace(parts[0]))
	if !ok {
		return 0, 0, false
	}
	end, ok := parseHM(strings.TrimSpace(parts[1]))
	if !ok {
		return 0, 0, false
	}
	return start, end, true
}

// parseHM parses "HH:MM" into minutes from midnight.
func parseHM(s string) (int, bool) {
	parts := strings.SplitN(s, ":", 2)
	if len(parts) != 2 {
		return 0, false
	}
	h, err := strconv.Atoi(parts[0])
	if err != nil || h < 0 || h > 23 {
		return 0, false
	}
	m, err := strconv.Atoi(parts[1])
	if err != nil || m < 0 || m > 59 {
		return 0, false
	}
	return h*60 + m, true
}
