package domain

import (
	"errors"
	"strings"
	"time"
)

var (
	ErrNotFound              = errors.New("scout not found")
	ErrForbidden             = errors.New("scout forbidden")
	ErrValidation            = errors.New("scout validation")
	ErrNoCrawlerSession      = errors.New("crawler session required when dev_mode enabled")
	ErrCrawlerSessionExpired = errors.New("crawler session expired")
	ErrTopicRemoved          = errors.New("ScoutTopic CRUD removed")
	ErrHasProducts           = errors.New("brand has products; remove products first")
	ErrIllegalRunStatus      = errors.New("illegal scout run status transition")
)

func NowNano() int64 { return time.Now().UTC().UnixNano() }

const (
	PathAPI     = "api"
	PathCrawler = "crawler"

	OutreachNew       = "new"
	OutreachDrafted   = "drafted"
	OutreachPublished = "published"
	OutreachSkipped   = "skipped"
	OutreachQueued    = "queued"

	ModeProduct  = "product"
	ModeTheme    = "theme"
	ModeActivity = "activity"
	ModeDemand   = "demand"
	ModeProvider = "provider"

	RunQueued    = "queued"
	RunRunning   = "running"
	RunSucceeded = "succeeded"
	RunFailed    = "failed"
	RunCancelled = "cancelled"

	ShortfallSourceExhausted    = "source_exhausted"
	ShortfallDuplicateExhausted = "duplicate_exhausted"
	ShortfallRelevanceExhausted = "relevance_exhausted"
	ShortfallSourceUnavailable  = "source_unavailable"
	ShortfallLimitReached       = "limit_reached"

	ClassificationSeekingHelp           = "seeking_help"
	ClassificationSeekingRecommendation = "seeking_recommendation"
	ClassificationProviderOffer         = "provider_offer"
	ClassificationDiscussion            = "discussion"
	ClassificationAsking                = "asking"
	ClassificationAnnouncement          = "announcement"
	ClassificationNoise                 = "noise"
	ClassificationProviderDirect        = "provider_direct"
	ClassificationProviderRecommended   = "provider_recommended"
)

const LegacyRunPrefix = "legacy:"

func LegacyRunID(themeKey string) string {
	if themeKey == "" {
		themeKey = "uncategorized"
	}
	return LegacyRunPrefix + themeKey
}

func LegacyThemeKey(runID string) (string, bool) {
	if !strings.HasPrefix(runID, LegacyRunPrefix) {
		return "", false
	}
	key := strings.TrimPrefix(runID, LegacyRunPrefix)
	if key == "uncategorized" {
		return "", true
	}
	return key, true
}

type Brand struct {
	ID             string `bson:"_id" json:"id"`
	OwnerUID       int64  `bson:"owner_uid" json:"owner_uid"`
	DisplayName    string `bson:"display_name" json:"display_name"`
	Brief          string `bson:"brief" json:"brief"`
	TargetAudience string `bson:"target_audience,omitempty" json:"target_audience,omitempty"`
	Goals          string `bson:"goals,omitempty" json:"goals,omitempty"`
	// Learning — growth-loop 資產複利
	LearningSummary       string `bson:"learning_summary,omitempty" json:"learning_summary,omitempty"`
	LearningVersion       int    `bson:"learning_version,omitempty" json:"learning_version,omitempty"`
	LearnedFromPostsCount int    `bson:"learned_from_posts_count,omitempty" json:"learned_from_posts_count,omitempty"`
	LastLearnedAt         int64  `bson:"last_learned_at,omitempty" json:"last_learned_at,omitempty"`
	CreatedAt             int64  `bson:"created_at" json:"created_at"`
	UpdatedAt             int64  `bson:"updated_at" json:"updated_at"`
}

type Product struct {
	ID                      string   `bson:"_id" json:"id"`
	OwnerUID                int64    `bson:"owner_uid" json:"owner_uid"`
	BrandID                 string   `bson:"brand_id" json:"brand_id"`
	Label                   string   `bson:"label" json:"label"`
	ProductContext          string   `bson:"product_context" json:"product_context"`
	MatchTags               []string `bson:"match_tags" json:"match_tags"`
	PainPoints              []string `bson:"pain_points" json:"pain_points"`
	ProviderCapabilityTerms []string `bson:"provider_capability_terms" json:"provider_capability_terms"`
	ProviderExcludeTerms    []string `bson:"provider_exclude_terms" json:"provider_exclude_terms"`
	PlacementURL            string   `bson:"placement_url,omitempty" json:"placement_url,omitempty"`
	CreatedAt               int64    `bson:"created_at" json:"created_at"`
	UpdatedAt               int64    `bson:"updated_at" json:"updated_at"`
}

type ActiveBrand struct {
	OwnerUID  int64  `bson:"_id" json:"owner_uid"`
	BrandID   string `bson:"brand_id" json:"brand_id"`
	UpdatedAt int64  `bson:"updated_at" json:"updated_at"`
}

type RunBrief struct {
	Intent         string   `json:"intent"`
	Mode           string   `json:"mode"`
	BrandID        string   `json:"brand_id,omitempty"`
	ProductID      string   `json:"product_id,omitempty"`
	ProductLabel   string   `json:"product_label,omitempty"`
	Pains          []string `json:"pains"`
	Tags           []string `json:"tags"`
	Periphery      []string `json:"periphery"`
	ScanTerms      []string `json:"scan_terms"`
	PlacementNote  string   `json:"placement_note,omitempty"`
	ResponseStance string   `json:"response_stance,omitempty"`
	ThemeKey       string   `json:"theme_key,omitempty"`
	ThemeLabel     string   `json:"theme_label,omitempty"`
	ProductContext string   `json:"product_context,omitempty"`
	// TargetCount is the desired hit count for this scan (topic daily goal).
	// When >0, RunScanFromBrief may top up (boost per-query, then secondary path).
	TargetCount int `json:"target_count,omitempty"`
}

// Run is one immutable scan attempt. theme_key groups intent only; ID keeps
// repeated scans of the same theme independent and auditable.
type Run struct {
	ID               string   `bson:"_id" json:"id"`
	OwnerUID         int64    `bson:"owner_uid" json:"owner_uid"`
	JobID            string   `bson:"job_id" json:"job_id"`
	ThemeKey         string   `bson:"theme_key" json:"theme_key"`
	ThemeLabel       string   `bson:"theme_label" json:"theme_label"`
	Intent           string   `bson:"intent" json:"intent"`
	Mode             string   `bson:"mode" json:"mode"`
	BrandID          string   `bson:"brand_id,omitempty" json:"brand_id,omitempty"`
	TargetCount      int      `bson:"target_count" json:"target_count"`
	Status           string   `bson:"status" json:"status"`
	SearchedCount    int      `bson:"searched_count" json:"searched_count"`
	DuplicateCount   int      `bson:"duplicate_count" json:"duplicate_count"`
	IrrelevantCount  int      `bson:"irrelevant_count" json:"irrelevant_count"`
	EligibleCount    int      `bson:"eligible_count" json:"eligible_count"`
	PendingCount     int      `bson:"pending_count" json:"pending_count"`
	ShortfallCount   int      `bson:"shortfall_count" json:"shortfall_count"`
	ShortfallReasons []string `bson:"shortfall_reasons" json:"shortfall_reasons"`
	CreatedAt        int64    `bson:"created_at" json:"created_at"`
	StartedAt        int64    `bson:"started_at,omitempty" json:"started_at,omitempty"`
	CompletedAt      int64    `bson:"completed_at,omitempty" json:"completed_at,omitempty"`
	Error            string   `bson:"error,omitempty" json:"error,omitempty"`
}

func NormalizeTargetCount(n int) int {
	if n <= 0 {
		return 20
	}
	if n > 40 {
		return 40
	}
	return n
}

func NewRun(id, jobID string, ownerUID int64, brief RunBrief, now int64) *Run {
	if now <= 0 {
		now = NowNano()
	}
	return &Run{
		ID: id, OwnerUID: ownerUID, JobID: jobID, ThemeKey: brief.ThemeKey,
		ThemeLabel: brief.ThemeLabel, Intent: brief.Intent, Mode: brief.Mode,
		BrandID: brief.BrandID, TargetCount: NormalizeTargetCount(brief.TargetCount),
		Status: RunQueued, ShortfallReasons: []string{}, CreatedAt: now,
	}
}

func IsRunTerminal(status string) bool {
	return status == RunSucceeded || status == RunFailed || status == RunCancelled
}

func CanTransitionRun(from, to string) bool {
	switch from {
	case RunQueued:
		return to == RunRunning || to == RunFailed || to == RunCancelled
	case RunRunning:
		return to == RunSucceeded || to == RunFailed || to == RunCancelled
	case RunSucceeded, RunFailed, RunCancelled:
		return false
	default:
		return false
	}
}

func (r *Run) Transition(to string, now int64) error {
	if r == nil || !CanTransitionRun(r.Status, to) {
		return ErrIllegalRunStatus
	}
	if now <= 0 {
		now = NowNano()
	}
	r.Status = to
	if to == RunRunning && r.StartedAt == 0 {
		r.StartedAt = now
	}
	if IsRunTerminal(to) {
		r.CompletedAt = now
	}
	return nil
}

type Post struct {
	ID                  string `bson:"_id" json:"id"`
	RunID               string `bson:"run_id,omitempty" json:"run_id,omitempty"`
	ExternalID          string `bson:"external_id,omitempty" json:"external_id,omitempty"`
	Permalink           string `bson:"permalink,omitempty" json:"permalink,omitempty"`
	OwnerUID            int64  `bson:"owner_uid" json:"owner_uid"`
	BrandID             string `bson:"brand_id,omitempty" json:"brand_id,omitempty"`
	Author              string `bson:"author" json:"author"`
	Text                string `bson:"text" json:"text"`
	SearchTag           string `bson:"search_tag" json:"search_tag"`
	Opportunity         string `bson:"opportunity" json:"opportunity"`
	OutreachStatus      string `bson:"outreach_status" json:"outreach_status"`
	DraftText           string `bson:"draft_text,omitempty" json:"draft_text,omitempty"`
	OutboxID            string `bson:"outbox_id,omitempty" json:"outbox_id,omitempty"`
	Score               int    `bson:"score" json:"score"`
	Classification      string `bson:"classification,omitempty" json:"classification,omitempty"`
	MatchedProductID    string `bson:"matched_product_id,omitempty" json:"matched_product_id,omitempty"`
	MatchedProductLabel string `bson:"matched_product_label,omitempty" json:"matched_product_label,omitempty"`
	MatchReason         string `bson:"match_reason,omitempty" json:"match_reason,omitempty"`
	ScoutMode           string `bson:"scout_mode,omitempty" json:"scout_mode,omitempty"`
	IntentSnippet       string `bson:"intent_snippet,omitempty" json:"intent_snippet,omitempty"`
	ThemeKey            string `bson:"theme_key,omitempty" json:"theme_key,omitempty"`
	ThemeLabel          string `bson:"theme_label,omitempty" json:"theme_label,omitempty"`
	ScanPath            string `bson:"scan_path,omitempty" json:"scan_path,omitempty"` // api|crawler
	// PostedAt = 原文發文時間（unix ns）；未知時為 0。列表以 score 優先，
	// 再用 PostedAt／CreatedAt／ID 做穩定 tie-breaker。
	PostedAt  int64 `bson:"posted_at,omitempty" json:"posted_at,omitempty"`
	CreatedAt int64 `bson:"created_at" json:"created_at"`
}

type Homework struct {
	ThemeKey   string   `bson:"_id" json:"theme_key"`
	OwnerUID   int64    `bson:"owner_uid" json:"owner_uid"`
	ThemeLabel string   `bson:"theme_label" json:"theme_label"`
	Purpose    string   `bson:"purpose" json:"purpose"`
	Brief      RunBrief `bson:"brief" json:"brief"`
	CreatedAt  int64    `bson:"created_at" json:"created_at"`
}

type CrawlerSession struct {
	OwnerUID        int64  `bson:"_id" json:"owner_uid"`
	StorageStateEnc string `bson:"storage_state_enc" json:"storage_state_enc"`
	UpdatedAt       int64  `bson:"updated_at" json:"updated_at"`
	ExpiresAt       int64  `bson:"expires_at" json:"expires_at"`
}

type ImportDraft struct {
	Label          string   `json:"label"`
	ProductContext string   `json:"product_context"`
	PainPoints     []string `json:"pain_points"`
	MatchTags      []string `json:"match_tags"`
	PlacementURL   string   `json:"placement_url"`
	SourceNote     string   `json:"source_note"`
}
