package job

import (
	"context"
	"fmt"
	"strings"

	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	"haixun-backend/internal/library/placement"
	libviral "haixun-backend/internal/library/viral"
	domai "haixun-backend/internal/model/ai/domain/usecase"
	aiusecase "haixun-backend/internal/model/ai/usecase"
	copydraftusecase "haixun-backend/internal/model/copy_draft/domain/usecase"
	missionentity "haixun-backend/internal/model/copy_mission/domain/entity"
	missiondomain "haixun-backend/internal/model/copy_mission/domain/usecase"
	jobdom "haixun-backend/internal/model/job/domain/usecase"
	personaentity "haixun-backend/internal/model/persona/domain/entity"
	personadomain "haixun-backend/internal/model/persona/domain/usecase"
	placementusecase "haixun-backend/internal/model/placement/usecase"
	scanpostusecase "haixun-backend/internal/model/scan_post/domain/usecase"
	threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase"
)

type ScanViralDeps struct {
	Jobs           jobdom.UseCase
	CopyMission    missiondomain.UseCase
	Persona        personadomain.UseCase
	ScanPost       scanpostusecase.UseCase
	CopyDraft      copydraftusecase.UseCase
	ThreadsAccount threadsaccountdomain.UseCase
	Placement      placementusecase.UseCase
	AI             aiusecase.UseCase
}

func RegisterScanViralHandler(runner *Runner, deps ScanViralDeps) {
	if runner == nil {
		return
	}
	runner.RegisterStepHandler("viral_crawl", func(ctx context.Context, step StepContext) error {
		return runScanViral(ctx, step, deps)
	})
}

func runScanViral(ctx context.Context, step StepContext, deps ScanViralDeps) error {
	payload := step.Run.Payload
	tenantID, ownerUID := runActorFromPayload(payload, step.Run)
	personaID := personaIDFromPayload(payload)
	missionID := copyMissionIDFromPayload(payload)
	if tenantID == "" || ownerUID == "" || personaID == "" {
		return fmt.Errorf("scan-viral payload missing tenant_id, owner_uid, or persona_id")
	}

	persona, err := deps.Persona.Get(ctx, tenantID, ownerUID, personaID)
	if err != nil {
		return err
	}
	var mission *missiondomain.MissionSummary
	if missionID != "" {
		item, err := deps.CopyMission.Get(ctx, tenantID, ownerUID, personaID, missionID)
		if err != nil {
			return err
		}
		mission = item
	}

	research, err := deps.Placement.ResearchSettings(ctx, tenantID, ownerUID)
	if err != nil {
		return err
	}
	memberCtx, err := deps.ThreadsAccount.ResolveMemberPlacementContext(ctx, tenantID, ownerUID, research)
	if err != nil {
		return err
	}
	if !memberCtx.HasDiscoverPath() {
		return fmt.Errorf("爆款掃描需要 Threads API、Chrome Session 或 Web Search API（請檢查連線模式與搜尋來源）")
	}

	updateProgress := func(summary string, percentage int) {
		_ = step.Heartbeat(ctx)
		_, _ = deps.Jobs.UpdateProgress(ctx, jobdom.UpdateProgressRequest{
			JobID:      step.JobID,
			WorkerID:   step.WorkerID,
			Phase:      "viral_crawl",
			Summary:    summary,
			Percentage: percentage,
		})
	}

	bootstrap := boolField(payload, "bootstrap")
	if mission == nil && bootstrap && persona.CopyResearchMap.AudienceSummary == "" && len(persona.CopyResearchMap.SuggestedTags) == 0 {
		updateProgress("產生拷貝忍者研究地圖…", 8)
		if err := ensureCopyResearchMap(ctx, deps, tenantID, ownerUID, persona, memberCtx, updateProgress); err != nil {
			return err
		}
		persona, err = deps.Persona.Get(ctx, tenantID, ownerUID, personaID)
		if err != nil {
			return err
		}
	}

	keywords := stringSliceField(payload, "keywords")
	exclusions := persona.CopyResearchMap.Exclusions
	missionScan := mission != nil
	if missionScan {
		if len(keywords) == 0 {
			keywords = append([]string(nil), mission.SelectedTags...)
		}
		exclusions = mission.ResearchMap.Exclusions
		if len(keywords) == 0 {
			return fmt.Errorf("請先產生研究地圖並勾選搜尋標籤")
		}
	} else if len(keywords) == 0 {
		keywords = deriveViralKeywords(persona)
	}
	if len(keywords) == 0 {
		return fmt.Errorf("請提供爆款掃描關鍵字，或先完成研究地圖／對標帳號")
	}

	if missionScan && missionID != "" {
		updateProgress("清除舊爆款與產文草稿…", 8)
		if deps.ScanPost != nil {
			if err := deps.ScanPost.ClearCopyMissionViralScan(ctx, tenantID, ownerUID, personaID, missionID); err != nil {
				return err
			}
		}
		if deps.CopyDraft != nil {
			if err := deps.CopyDraft.ClearByMission(ctx, tenantID, ownerUID, personaID, missionID); err != nil {
				return err
			}
		}
	}

	updateProgress("準備爆款掃描…", 12)
	crawlerFn := makeCrawlerSearchFn(ScanPlacementDeps{ThreadsAccount: deps.ThreadsAccount}, tenantID, ownerUID)
	candidates, err := libviral.RunDiscover(ctx, libviral.DiscoverInput{
		Keywords:    keywords,
		Exclusions:  exclusions,
		Member:      memberCtx,
		Crawler:     crawlerFn,
		MissionScan: missionScan,
	}, updateProgress)
	if err != nil {
		return err
	}

	if missionScan && memberCtx.ScrapeReplies && memberCtx.ApiConnected {
		updateProgress("收集高互動留言樣本…", 88)
		candidates = placement.AttachReplies(ctx, placement.ScrapeRepliesInput{
			Posts:          candidates,
			Member:         memberCtx,
			RepliesPerPost: memberCtx.RepliesPerPost,
			MaxPosts:       6,
		})
	}

	updateProgress(fmt.Sprintf("寫入 %d 篇爆款候選…", len(candidates)), 92)
	count, err := deps.ScanPost.ReplaceFromViralScan(ctx, scanpostusecase.ViralReplaceRequest{
		TenantID:      tenantID,
		OwnerUID:      ownerUID,
		PersonaID:     personaID,
		CopyMissionID: missionID,
		ScanJobID:     step.JobID,
		Posts:         candidates,
	})
	if err != nil {
		return err
	}

	if missionScan && missionID != "" && mission != nil {
		scanned := missionentity.StatusScanned
		jobID := step.JobID
		referenceAccounts := libviral.BuildReferenceAccountsFromScan(libviral.ReferenceAccountInput{
			SeedQuery: mission.SeedQuery,
			Label:     mission.Label,
			Posts:     candidates,
			Limit:     libviral.MaxSimilarAccounts,
		})
		entityTags := make([]missionentity.SuggestedTag, 0, len(mission.ResearchMap.SuggestedTags))
		for _, tag := range mission.ResearchMap.SuggestedTags {
			entityTags = append(entityTags, missionentity.SuggestedTag{
				Tag:          tag.Tag,
				Reason:       tag.Reason,
				SearchIntent: tag.SearchIntent,
				SearchType:   tag.SearchType,
			})
		}
		updatedMap := missionentity.ResearchMap{
			AudienceSummary: mission.ResearchMap.AudienceSummary,
			ContentGoal:     mission.ResearchMap.ContentGoal,
			Questions:       append([]string(nil), mission.ResearchMap.Questions...),
			Pillars:         append([]string(nil), mission.ResearchMap.Pillars...),
			Exclusions:      append([]string(nil), mission.ResearchMap.Exclusions...),
			SuggestedTags:   entityTags,
			SimilarAccounts: referenceAccounts,
			BenchmarkNotes:  mission.ResearchMap.BenchmarkNotes,
		}
		_, _ = deps.CopyMission.Update(ctx, missiondomain.UpdateRequest{
			TenantID:  tenantID,
			OwnerUID:  ownerUID,
			PersonaID: personaID,
			MissionID: missionID,
			Patch: missiondomain.MissionPatch{
				LastScanJobID: &jobID,
				Status:        &scanned,
				ResearchMap:   &updatedMap,
			},
		})
	}

	nextRoute := "/matrix"
	if missionID != "" {
		nextRoute = fmt.Sprintf("/matrix/missions/%s", missionID)
	}
	handoff := map[string]any{
		"flow":            "copy",
		"persona_id":      personaID,
		"copy_mission_id": missionID,
		"summary":         fmt.Sprintf("爆款掃描完成：%d 篇候選", count),
		"next_route":      nextRoute,
	}

	_, err = deps.Jobs.CompleteRun(ctx, jobdom.CompleteRunRequest{
		JobID:    step.JobID,
		WorkerID: step.WorkerID,
		Result: map[string]any{
			"post_count": count,
			"handoff":    handoff,
		},
	})
	return err
}

func ensureCopyResearchMap(
	ctx context.Context,
	deps ScanViralDeps,
	tenantID, ownerUID string,
	persona *personadomain.PersonaSummary,
	memberCtx placement.MemberContext,
	updateProgress func(string, int),
) error {
	if persona == nil {
		return fmt.Errorf("copy research map: missing persona")
	}
	credential, err := deps.ThreadsAccount.ResolveMemberAiCredential(ctx, tenantID, ownerUID)
	if err != nil {
		return err
	}
	providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
	if err != nil {
		return err
	}

	label := strings.TrimSpace(persona.DisplayName)
	if label == "" {
		label = "拷貝主題"
	}
	seed := strings.TrimSpace(persona.SeedQuery)
	if seed == "" {
		seed = strings.TrimPrefix(strings.TrimSpace(persona.StyleBenchmark), "@")
	}
	if seed == "" {
		for _, line := range strings.Split(strings.TrimSpace(persona.Brief), "\n") {
			line = strings.TrimSpace(line)
			if line != "" {
				seed = line
				break
			}
		}
	}

	result, err := deps.AI.GenerateText(ctx, domai.GenerateRequest{
		Provider: providerID,
		Model:    credential.Model,
		Credential: domai.Credential{
			APIKey: credential.APIKey,
		},
		System: libviral.BuildCopyResearchMapSystemPrompt(),
		Messages: []domai.Message{
			{
				Role: "user",
				Content: libviral.BuildCopyResearchMapUserPrompt(libviral.CopyResearchMapInput{
					Label:          label,
					SeedQuery:      seed,
					Brief:          persona.Brief,
					Persona:        persona.Persona,
					StyleBenchmark: persona.StyleBenchmark,
				}),
			},
		},
	})
	if err != nil {
		return err
	}

	parsed, err := libviral.ParseCopyResearchMapOutput(result.Text)
	if err != nil {
		return app.For(code.AI).SvcThirdParty("拷貝研究地圖 LLM 回傳無法解析：" + err.Error())
	}

	entityMap := personaentity.CopyResearchMap{
		AudienceSummary: parsed.AudienceSummary,
		ContentGoal:     parsed.ContentGoal,
		Questions:       parsed.Questions,
		Pillars:         parsed.Pillars,
		Exclusions:      parsed.Exclusions,
		SuggestedTags:   parsed.SuggestedTags,
		BenchmarkNotes:  parsed.BenchmarkNotes,
	}
	patch := personadomain.PersonaPatch{
		CopyResearchMap: &entityMap,
	}
	if seed != "" && strings.TrimSpace(persona.SeedQuery) == "" {
		patch.SeedQuery = &seed
	}
	_, err = deps.Persona.Update(ctx, personadomain.UpdateRequest{
		TenantID:  tenantID,
		OwnerUID:  ownerUID,
		PersonaID: persona.ID,
		Patch:     patch,
	})
	if err != nil {
		return err
	}
	if updateProgress != nil {
		updateProgress("研究地圖已就緒", 10)
	}
	_ = memberCtx
	return nil
}

func personaIDFromPayload(payload map[string]any) string {
	if id := stringField(payload, "persona_id"); id != "" {
		return id
	}
	return stringField(payload, "scope_id")
}

func deriveViralKeywords(persona *personadomain.PersonaSummary) []string {
	if persona == nil {
		return nil
	}
	out := []string{}
	seen := map[string]struct{}{}
	add := func(kw string) {
		kw = strings.TrimSpace(kw)
		if kw == "" {
			return
		}
		if _, ok := seen[kw]; ok {
			return
		}
		seen[kw] = struct{}{}
		out = append(out, kw)
	}
	for _, tag := range persona.CopyResearchMap.SuggestedTags {
		add(tag)
	}
	for _, q := range persona.CopyResearchMap.Questions {
		add(q)
	}
	if bench := strings.TrimPrefix(strings.TrimSpace(persona.StyleBenchmark), "@"); bench != "" {
		add(bench)
	}
	for _, line := range strings.Split(strings.TrimSpace(persona.Brief), "\n") {
		line = strings.TrimSpace(line)
		if line != "" {
			add(line)
			break
		}
	}
	return out
}
