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"
	"haixun-backend/internal/library/websearch"
	domai "haixun-backend/internal/model/ai/domain/usecase"
	aiusecase "haixun-backend/internal/model/ai/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"
	personadomain "haixun-backend/internal/model/persona/domain/usecase"
	placementusecase "haixun-backend/internal/model/placement/usecase"
	threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase"
)

type AnalyzeCopyMissionDeps struct {
	Jobs           jobdom.UseCase
	CopyMission    missiondomain.UseCase
	Persona        personadomain.UseCase
	ThreadsAccount threadsaccountdomain.UseCase
	Placement      placementusecase.UseCase
	AI             aiusecase.UseCase
}

func RegisterAnalyzeCopyMissionHandler(runner *Runner, deps AnalyzeCopyMissionDeps) {
	if runner == nil {
		return
	}
	runner.RegisterStepHandler("copy_mission_map", func(ctx context.Context, step StepContext) error {
		return runAnalyzeCopyMission(ctx, step, deps)
	})
}

func runAnalyzeCopyMission(ctx context.Context, step StepContext, deps AnalyzeCopyMissionDeps) error {
	payload := step.Run.Payload
	tenantID, ownerUID := runActorFromPayload(payload, step.Run)
	personaID := stringField(payload, "persona_id")
	missionID := stringField(payload, "copy_mission_id")
	if tenantID == "" || ownerUID == "" || personaID == "" || missionID == "" {
		return fmt.Errorf("analyze-copy-mission payload missing tenant_id, owner_uid, persona_id, or copy_mission_id")
	}

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

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

	updateProgress("產生拷貝任務研究地圖…", 15)

	credential, err := deps.ThreadsAccount.ResolveMemberAiCredential(ctx, tenantID, ownerUID)
	if err != nil {
		return err
	}
	providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
	if err != nil {
		return err
	}

	result, err := deps.AI.GenerateText(ctx, domai.GenerateRequest{
		Provider: providerID,
		Model:    credential.Model,
		Credential: domai.Credential{
			APIKey: credential.APIKey,
		},
		System: libviral.BuildMissionResearchMapSystemPrompt(),
		Messages: []domai.Message{
			{
				Role: "user",
				Content: libviral.BuildMissionResearchMapUserPrompt(libviral.CopyResearchMapInput{
					Label:                  mission.Label,
					SeedQuery:              mission.SeedQuery,
					Brief:                  mission.Brief,
					Persona:                persona.Persona,
					StyleBenchmark:         persona.StyleBenchmark,
					PersonaAudienceSummary: persona.CopyResearchMap.AudienceSummary,
					PersonaContentGoal:     persona.CopyResearchMap.ContentGoal,
					PersonaQuestions:       append([]string(nil), persona.CopyResearchMap.Questions...),
					PersonaPillars:         append([]string(nil), persona.CopyResearchMap.Pillars...),
				}),
			},
		},
	})
	if err != nil {
		return err
	}

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

	entityTags := make([]missionentity.SuggestedTag, 0, len(parsed.SuggestedTags))
	for _, tag := range parsed.SuggestedTags {
		entityTags = append(entityTags, missionentity.SuggestedTag{
			Tag:          tag.Tag,
			Reason:       tag.Reason,
			SearchIntent: tag.SearchIntent,
			SearchType:   tag.SearchType,
		})
	}
	researchMap := missionentity.ResearchMap{
		AudienceSummary: parsed.AudienceSummary,
		ContentGoal:     parsed.ContentGoal,
		Questions:       parsed.Questions,
		Pillars:         parsed.Pillars,
		Exclusions:      parsed.Exclusions,
		KnowledgeItems:  baseKnowledgeItems(parsed),
		BenchmarkNotes:  parsed.BenchmarkNotes,
		SimilarAccounts: similarAccountsFromSummary(mission.ResearchMap.SimilarAccounts),
	}
	researchMap.SuggestedTags = entityTags
	updateProgress("補充相似帳號與延伸知識…", 70)
	enrichMissionResearchMap(ctx, deps, tenantID, ownerUID, mission, &researchMap)
	if len(researchMap.SelectedKnowledgeItems) == 0 {
		researchMap.SelectedKnowledgeItems = defaultSelectedKnowledgeItems(researchMap.KnowledgeItems)
	}
	selected := libviral.PickDefaultSelectedTags(parsed.SuggestedTags)
	selected = ensureSeedQuerySelected(mission.SeedQuery, selected)

	mapped := missionentity.StatusMapped
	updateProgress("儲存研究地圖與預設標籤…", 85)
	_, err = deps.CopyMission.Update(ctx, missiondomain.UpdateRequest{
		TenantID:  tenantID,
		OwnerUID:  ownerUID,
		PersonaID: personaID,
		MissionID: missionID,
		Patch: missiondomain.MissionPatch{
			ResearchMap:     &researchMap,
			SelectedTagsSet: true,
			SelectedTags:    selected,
			Status:          &mapped,
		},
	})
	if err != nil {
		return err
	}

	handoff := map[string]any{
		"flow":            "copy",
		"persona_id":      personaID,
		"copy_mission_id": missionID,
		"summary":         fmt.Sprintf("研究地圖就緒，已預選 %d 個搜尋標籤", len(selected)),
		"next_route":      fmt.Sprintf("/matrix/missions/%s", missionID),
	}

	_, err = deps.Jobs.CompleteRun(ctx, jobdom.CompleteRunRequest{
		JobID:    step.JobID,
		WorkerID: step.WorkerID,
		Result: map[string]any{
			"tag_count":      len(entityTags),
			"account_count":  0,
			"selected_count": len(selected),
			"handoff":        handoff,
		},
	})
	return err
}

func enrichMissionResearchMap(
	ctx context.Context,
	deps AnalyzeCopyMissionDeps,
	tenantID, ownerUID string,
	mission *missiondomain.MissionSummary,
	researchMap *missionentity.ResearchMap,
) {
	if mission == nil || researchMap == nil || deps.Placement == nil || deps.ThreadsAccount == nil {
		return
	}
	settings, err := deps.Placement.ResearchSettings(ctx, tenantID, ownerUID)
	if err != nil || !placement.WebSearchAvailable(settings) {
		return
	}
	memberCtx, err := deps.ThreadsAccount.ResolveMemberPlacementContext(ctx, tenantID, ownerUID, settings)
	if err != nil || memberCtx.WebSearchAPIKey() == "" {
		return
	}
	client := websearch.New(memberCtx.WebSearchConfig())
	if !client.Enabled() {
		return
	}
	if len(researchMap.SimilarAccounts) < libviral.MaxSimilarAccounts {
		webAccounts, err := libviral.DiscoverSimilarAccounts(ctx, client, libviral.DiscoverAccountsInput{
			SeedQuery: mission.SeedQuery,
			Brief:     mission.Brief,
			Pillars:   researchMap.Pillars,
		})
		if err == nil && len(webAccounts) > 0 {
			researchMap.SimilarAccounts = libviral.MergeSimilarAccounts(
				researchMap.SimilarAccounts,
				similarAccountEntitiesFromWeb(webAccounts),
			)
		}
	}
	if notes := collectMissionKnowledgeNotes(ctx, client, memberCtx, mission, researchMap); len(notes) > 0 {
		researchMap.KnowledgeItems = mergeStringLists(researchMap.KnowledgeItems, notes)
	}
}

func similarAccountEntitiesFromWeb(in []libviral.SimilarAccount) []missionentity.SimilarAccount {
	out := make([]missionentity.SimilarAccount, 0, len(in))
	for _, item := range in {
		out = append(out, missionentity.SimilarAccount{
			Username:      item.Username,
			Reason:        item.Reason,
			Source:        item.Source,
			MatchedSource: item.MatchedSource,
			Confidence:    item.Confidence,
			Status:        missionentity.SimilarAccountStatusRecommended,
			ProfileURL:    item.ProfileURL,
		})
	}
	return out
}

func collectMissionKnowledgeNotes(
	ctx context.Context,
	client websearch.Client,
	member placement.MemberContext,
	mission *missiondomain.MissionSummary,
	researchMap *missionentity.ResearchMap,
) []string {
	seed := strings.TrimSpace(mission.SeedQuery)
	if seed == "" {
		return nil
	}
	queries := []string{seed + " 重點 懶人包"}
	if len(researchMap.Questions) > 0 {
		if q := strings.TrimSpace(researchMap.Questions[0]); q != "" && q != seed {
			queries = append(queries, q)
		}
	}
	seen := map[string]struct{}{}
	lines := []string{}
	for _, query := range queries {
		if len(lines) >= 5 {
			break
		}
		res, err := client.Search(ctx, websearch.SearchOptions{
			Query:        query,
			Limit:        4,
			Mode:         websearch.ModeKnowledgeExpand,
			Country:      member.BraveCountry,
			SearchLang:   member.BraveSearchLang,
			UserLocation: member.ExaUserLocation,
		})
		if err != nil || res.Status != "success" {
			continue
		}
		for _, item := range res.Results {
			if len(lines) >= 5 {
				break
			}
			title := strings.TrimSpace(item.Title)
			snippet := strings.TrimSpace(item.Snippet)
			if title == "" && snippet == "" {
				continue
			}
			key := strings.ToLower(title + "|" + snippet)
			if _, ok := seen[key]; ok {
				continue
			}
			seen[key] = struct{}{}
			line := title
			if snippet != "" {
				if len([]rune(snippet)) > 120 {
					snippet = string([]rune(snippet)[:120])
				}
				line += "：" + snippet
			}
			lines = append(lines, line)
		}
	}
	if len(lines) == 0 {
		return nil
	}
	return lines
}

func baseKnowledgeItems(parsed libviral.MissionResearchMap) []string {
	items := []string{}
	for _, pillar := range parsed.Pillars {
		if strings.TrimSpace(pillar) != "" {
			items = append(items, "內容支柱："+strings.TrimSpace(pillar))
		}
	}
	for _, question := range parsed.Questions {
		if strings.TrimSpace(question) != "" {
			items = append(items, "受眾問題："+strings.TrimSpace(question))
		}
	}
	if strings.TrimSpace(parsed.BenchmarkNotes) != "" {
		items = append(items, "研究筆記："+strings.TrimSpace(parsed.BenchmarkNotes))
	}
	return mergeStringLists(nil, items)
}

func defaultSelectedKnowledgeItems(items []string) []string {
	if len(items) == 0 {
		return nil
	}
	if len(items) > 6 {
		return append([]string(nil), items[:6]...)
	}
	return append([]string(nil), items...)
}

func mergeStringLists(base []string, extra []string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0, len(base)+len(extra))
	for _, item := range append(append([]string{}, base...), extra...) {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		if _, ok := seen[item]; ok {
			continue
		}
		seen[item] = struct{}{}
		out = append(out, item)
	}
	return out
}

func ensureSeedQuerySelected(seed string, selected []string) []string {
	seed = strings.TrimSpace(seed)
	if seed == "" {
		return selected
	}
	for _, item := range selected {
		if strings.EqualFold(strings.TrimSpace(item), seed) {
			return selected
		}
	}
	out := append([]string{seed}, selected...)
	if len(out) > libviral.MaxScanTags {
		out = out[:libviral.MaxScanTags]
	}
	return out
}

func copyMissionIDFromPayload(payload map[string]any) string {
	if id := stringField(payload, "copy_mission_id"); id != "" {
		return id
	}
	return strings.TrimSpace(stringField(payload, "mission_id"))
}
