package usecase

import (
	"context"
	"fmt"
	"strings"
	"time"

	"apps/backend/internal/module/radar/domain"
)

// DailySweepHourUTC is the fixed daily schedule (spec §4.2：UTC 22:00 ＝台北 06:00)。
const DailySweepHourUTC = 22

// SweepJobScheduler schedules one radar_sweep job per watch per UTC day.
// Implemented by a thin adapter over job usecase.Service (see worker / service_context).
type SweepJobScheduler interface {
	ScheduleRadarSweep(ctx context.Context, ownerUID int64, watchID string, runAt int64) (jobID string, err error)
}

// ManualSweepJobScheduler is optional. TriggerSweep uses it so 「立即巡邏」
// is not swallowed by a finished same-day daily job.
type ManualSweepJobScheduler interface {
	ScheduleManualRadarSweep(ctx context.Context, ownerUID int64, watchID string, runAt int64) (jobID string, err error)
}

// SweepJobSchedulerFunc adapts a function to SweepJobScheduler.
type SweepJobSchedulerFunc func(ctx context.Context, ownerUID int64, watchID string, runAt int64) (jobID string, err error)

func (f SweepJobSchedulerFunc) ScheduleRadarSweep(ctx context.Context, ownerUID int64, watchID string, runAt int64) (string, error) {
	return f(ctx, ownerUID, watchID, runAt)
}

// ScheduleDailySweeps 在已過當日 UTC 22:00 時，為每個 active watch 建一筆 radar_sweep Job。
//
// 呼叫端必須已持有 worker maintenance Redis lock（value = workerID），
// 本函式本身不做分散式鎖；未過 22:00 時回 0 且不建 Job。
//
// 回傳建立（或已存在而回傳）的 job 數。
func (s *Service) ScheduleDailySweeps(ctx context.Context, now time.Time) (int, error) {
	if s.SweepJobs == nil {
		return 0, fmt.Errorf("%w: sweep job scheduler not configured", domain.ErrNotReady)
	}
	if now.IsZero() {
		now = time.Now().UTC()
	} else {
		now = now.UTC()
	}
	if !PastDailySweepSlot(now) {
		return 0, nil
	}
	runAt := DailySweepRunAt(now)

	watches, err := s.Repo.ListAllActiveWatches(ctx)
	if err != nil {
		return 0, err
	}
	n := 0
	for _, w := range watches {
		if w == nil || w.Status != domain.WatchActive {
			continue
		}
		if _, err := s.SweepJobs.ScheduleRadarSweep(ctx, w.OwnerUID, w.ID, runAt); err != nil {
			return n, fmt.Errorf("schedule watch %s owner %d: %w", w.ID, w.OwnerUID, err)
		}
		n++
	}
	return n, nil
}

// PastDailySweepSlot reports whether now is at or after today's UTC 22:00.
func PastDailySweepSlot(now time.Time) bool {
	now = now.UTC()
	slot := time.Date(now.Year(), now.Month(), now.Day(), DailySweepHourUTC, 0, 0, 0, time.UTC)
	return !now.Before(slot)
}

// DailySweepRunAt returns unix-ns for today's UTC 22:00 (the slot being scheduled).
func DailySweepRunAt(now time.Time) int64 {
	now = now.UTC()
	slot := time.Date(now.Year(), now.Month(), now.Day(), DailySweepHourUTC, 0, 0, 0, time.UTC)
	return slot.UnixNano()
}

// BeginSweepRecord creates the RadarSweep shell for a claimed job (T527).
// Fetch / judge (T528–T530) attach progress onto the same record via UpdateSweep.
func (s *Service) BeginSweepRecord(ctx context.Context, ownerUID int64, watchID, jobID, path string) (*domain.RadarSweep, error) {
	if ownerUID <= 0 {
		return nil, fmt.Errorf("%w: owner_uid required", domain.ErrValidation)
	}
	// watchID may be empty for on-demand explore (no subscription).
	if path == "" {
		path = domain.SweepPathAPI
	}
	sw := &domain.RadarSweep{
		ID:        domain.NewID(),
		OwnerUID:  ownerUID,
		WatchID:   strings.TrimSpace(watchID),
		JobID:     jobID,
		Path:      path,
		StartedAt: domain.NowNano(),
	}
	if err := sw.Normalize(); err != nil {
		return nil, err
	}
	if err := s.Repo.CreateSweep(ctx, sw); err != nil {
		return nil, err
	}
	return sw, nil
}
