package monitor

import (
	"context"
	"log"
	"time"

	"github.com/rose_cat707/luminary/internal/gateway"
	"github.com/rose_cat707/luminary/internal/model"
	"github.com/rose_cat707/luminary/internal/settings"
	"github.com/rose_cat707/luminary/internal/store/cache"
	sqlitestore "github.com/rose_cat707/luminary/internal/store/sqlite"
	"gorm.io/gorm"
)

type Service struct {
	gateway       *gateway.Service
	cache         *cache.Store
	db            *gorm.DB
	settings      *settings.Runtime
	stop          chan struct{}
	healthRestart chan struct{}
	flushRestart  chan struct{}
	flushFn       func([]cache.UsageDelta)
}

func New(gw *gateway.Service, c *cache.Store, store *sqlitestore.Store, rt *settings.Runtime) *Service {
	return &Service{
		gateway:       gw,
		cache:         c,
		db:            store.DB(),
		settings:      rt,
		stop:          make(chan struct{}),
		healthRestart: make(chan struct{}, 1),
		flushRestart:  make(chan struct{}, 1),
	}
}

func (s *Service) Start(flushFn func([]cache.UsageDelta)) {
	s.flushFn = flushFn
	go s.healthLoop()
	go s.usageFlushLoop()
	go s.dailyResetLoop()
}

func (s *Service) Stop() {
	close(s.stop)
}

func (s *Service) NotifySettingsChanged() {
	select {
	case s.healthRestart <- struct{}{}:
	default:
	}
	select {
	case s.flushRestart <- struct{}{}:
	default:
	}
}

func (s *Service) healthInterval() time.Duration {
	interval := s.settings.HealthCheckInterval()
	if interval < time.Second {
		return 30 * time.Second
	}
	return interval
}

func (s *Service) flushInterval() time.Duration {
	interval := s.settings.UsageFlushInterval()
	if interval < time.Second {
		return 5 * time.Second
	}
	return interval
}

func (s *Service) healthLoop() {
	var ticker *time.Ticker
	resetTicker := func() {
		if ticker != nil {
			ticker.Stop()
		}
		ticker = time.NewTicker(s.healthInterval())
	}
	resetTicker()
	defer ticker.Stop()
	s.runChecks()
	for {
		select {
		case <-ticker.C:
			s.runChecks()
		case <-s.healthRestart:
			resetTicker()
		case <-s.stop:
			return
		}
	}
}

func (s *Service) usageFlushLoop() {
	var ticker *time.Ticker
	resetTicker := func() {
		if ticker != nil {
			ticker.Stop()
		}
		ticker = time.NewTicker(s.flushInterval())
	}
	resetTicker()
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			s.flushUsage()
		case <-s.flushRestart:
			resetTicker()
		case <-s.stop:
			s.flushUsage()
			return
		}
	}
}

func (s *Service) flushUsage() {
	if s.flushFn == nil {
		return
	}
	deltas := s.cache.DrainUsageDeltas()
	if len(deltas) > 0 {
		s.flushFn(deltas)
	}
}

func (s *Service) runChecks() {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()
	for _, p := range s.cache.ListProviders() {
		status, latency, errMsg := s.gateway.HealthCheckProvider(ctx, p.ID)
		rec := model.HealthCheckRecord{
			ProviderID: p.ID,
			Status:     status,
			LatencyMs:  latency,
			ErrorMsg:   errMsg,
			CheckedAt:  time.Now(),
		}
		s.cache.SetHealthStatus(p.ID, rec)
		if err := s.db.Create(&rec).Error; err != nil {
			log.Printf("save health check: %v", err)
		}
	}
}

func (s *Service) dailyResetLoop() {
	for {
		now := time.Now()
		next := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
		timer := time.NewTimer(time.Until(next))
		select {
		case <-timer.C:
			s.resetDailyCounters()
		case <-s.stop:
			timer.Stop()
			return
		}
	}
}

func (s *Service) resetDailyCounters() {
	s.cache.ResetProviderKeyDailyCounts()
	if err := s.db.Model(&model.ProviderKey{}).Updates(map[string]any{
		"requests_today": 0,
		"tokens_today":   0,
	}).Error; err != nil {
		log.Printf("reset daily provider key counters: %v", err)
	}
}
