package domain

import (
	"fmt"
	"strings"
)

// Sweep fetch path — reuses the dual-path split (api | crawler); no third path.
const (
	SweepPathAPI     = "api"
	SweepPathCrawler = "crawler"
)

/*
RadarSweep 是一次每日巡（或手動觸發）的執行紀錄。

計數以累加更新為主：Job 中途失敗時已寫入的進度保留，供續跑判斷
（judged_external_ids 標記已判過的貼文，避免重跑）。
*/
type RadarSweep struct {
	ID                     string   `bson:"_id" json:"id"`
	OwnerUID               int64    `bson:"owner_uid" json:"owner_uid"`
	WatchID                string   `bson:"watch_id" json:"watch_id"`
	JobID                  string   `bson:"job_id,omitempty" json:"job_id,omitempty"`
	Path                   string   `bson:"path" json:"path"`
	HitCount               int      `bson:"hit_count" json:"hit_count"`
	JudgedCount            int      `bson:"judged_count" json:"judged_count"`
	CreatedCount           int      `bson:"created_count" json:"created_count"`
	TruncatedCount         int      `bson:"truncated_count" json:"truncated_count"`
	MatchEvaluatedCount    int      `bson:"match_evaluated_count" json:"match_evaluated_count"`
	MatchMergedCount       int      `bson:"match_merged_count" json:"match_merged_count"`
	FitRejectedCount       int      `bson:"fit_rejected_count" json:"fit_rejected_count"`
	FailedReason           string   `bson:"failed_reason,omitempty" json:"failed_reason,omitempty"`
	CreditsUsed            int      `bson:"credits_used" json:"credits_used"`
	DedupedCount           int      `bson:"deduped_count" json:"deduped_count"`
	PrefilterPassCount     int      `bson:"prefilter_pass_count" json:"prefilter_pass_count"`
	PrefilterReviewCount   int      `bson:"prefilter_review_count" json:"prefilter_review_count"`
	PrefilterRejectedCount int      `bson:"prefilter_rejected_count" json:"prefilter_rejected_count"`
	CachedJudgmentCount    int      `bson:"cached_judgment_count" json:"cached_judgment_count"`
	TombstoneMatchedCount  int      `bson:"tombstone_matched_count" json:"tombstone_matched_count"`
	BudgetDeferredCount    int      `bson:"budget_deferred_count" json:"budget_deferred_count"`
	DemandInputVersion     string   `bson:"demand_input_version,omitempty" json:"demand_input_version,omitempty"`
	DemandMapVersion       int64    `bson:"demand_map_version,omitempty" json:"demand_map_version,omitempty"`
	CreditSearch           int      `bson:"credit_search" json:"credit_search"`
	CreditDemandMap        int      `bson:"credit_demand_map" json:"credit_demand_map"`
	CreditJudge            int      `bson:"credit_judge" json:"credit_judge"`
	CreditReply            int      `bson:"credit_reply" json:"credit_reply"`
	JudgedExternalIDs      []string `bson:"judged_external_ids,omitempty" json:"judged_external_ids,omitempty"`
	StartedAt              int64    `bson:"started_at" json:"started_at"`
	EndedAt                int64    `bson:"ended_at,omitempty" json:"ended_at,omitempty"`
}

// SweepDelta is an incremental progress patch applied with $inc / $addToSet.
type SweepDelta struct {
	HitCount               int
	JudgedCount            int
	CreatedCount           int
	TruncatedCount         int
	MatchEvaluatedCount    int
	MatchMergedCount       int
	FitRejectedCount       int
	CreditsUsed            int
	DedupedCount           int
	PrefilterPassCount     int
	PrefilterReviewCount   int
	PrefilterRejectedCount int
	CachedJudgmentCount    int
	TombstoneMatchedCount  int
	BudgetDeferredCount    int
	CreditSearch           int
	CreditDemandMap        int
	CreditJudge            int
	CreditReply            int
	JudgedExternalIDs      []string
	FailedReason           *string // nil = leave unchanged; non-nil (incl. empty) = set
	EndedAt                int64   // 0 = leave unchanged
}

// SweepListFilter pages sweeps for an owner, optionally scoped to one watch.
type SweepListFilter struct {
	WatchID  string
	Page     int
	PageSize int
}

func IsSweepPath(s string) bool {
	switch s {
	case SweepPathAPI, SweepPathCrawler:
		return true
	}
	return false
}

func (s *RadarSweep) Normalize() error {
	if s.OwnerUID <= 0 {
		return fmt.Errorf("%w: owner_uid required", ErrValidation)
	}
	// WatchID may be empty for on-demand explore (no subscription); required for scheduled sweeps.
	s.WatchID = strings.TrimSpace(s.WatchID)
	if s.Path == "" {
		s.Path = SweepPathAPI
	}
	if !IsSweepPath(s.Path) {
		return fmt.Errorf("%w: unknown sweep path %q", ErrValidation, s.Path)
	}
	// failed_reason must never hold tokens; strip obvious bearer-like blobs is out of scope —
	// callers are responsible. We only reject empty path/owner.
	if s.FailedReason != "" {
		s.FailedReason = strings.TrimSpace(s.FailedReason)
	}
	s.JudgedExternalIDs = dedupeStrings(s.JudgedExternalIDs)
	return nil
}

// MergeJudgedExternalIDs appends new IDs without duplicates (order preserved).
func MergeJudgedExternalIDs(existing, add []string) []string {
	return dedupeStrings(append(append([]string{}, existing...), add...))
}
