package job

import (
	"context"
	"fmt"
	"strings"

	"haixun-backend/internal/library/clock"
	libcopy "haixun-backend/internal/library/copymission"
	app "haixun-backend/internal/library/errors"
	"haixun-backend/internal/library/errors/code"
	libkg "haixun-backend/internal/library/knowledge"
	"haixun-backend/internal/library/placement"
	libprompt "haixun-backend/internal/library/prompt"
	"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"
	kgusecase "haixun-backend/internal/model/knowledge_graph/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 ExpandCopyMissionGraphDeps struct {
	Jobs           jobdom.UseCase
	CopyMission    missiondomain.UseCase
	Persona        personadomain.UseCase
	KnowledgeGraph kgusecase.UseCase
	ThreadsAccount threadsaccountdomain.UseCase
	Placement      placementusecase.UseCase
	AI             aiusecase.UseCase
}

func RegisterExpandCopyMissionGraphHandler(runner *Runner, deps ExpandCopyMissionGraphDeps) {
	if runner == nil {
		return
	}
	runner.RegisterStepHandler("expand_copy_knowledge", func(ctx context.Context, step StepContext) error {
		return runExpandCopyMissionGraph(ctx, step, deps)
	})
}

func runExpandCopyMissionGraph(ctx context.Context, step StepContext, deps ExpandCopyMissionGraphDeps) error {
	payload := step.Run.Payload
	tenantID, ownerUID := runActorFromPayload(payload, step.Run)
	personaID := stringField(payload, "persona_id")
	missionID := copyMissionIDFromPayload(payload)
	seed := stringField(payload, "seed_query")
	supplemental := boolField(payload, "supplemental")
	if tenantID == "" || ownerUID == "" || personaID == "" || missionID == "" {
		return fmt.Errorf("expand-copy-mission-graph payload missing tenant_id, owner_uid, persona_id, or copy_mission_id")
	}
	if seed == "" {
		return fmt.Errorf("expand-copy-mission-graph payload missing seed_query")
	}

	mission, err := deps.CopyMission.Get(ctx, tenantID, ownerUID, personaID, missionID)
	if err != nil {
		return err
	}
	if strings.TrimSpace(mission.ResearchMap.AudienceSummary) == "" {
		return app.For(code.Persona).InputMissingRequired("請先產生研究地圖再擴展延伸知識")
	}
	persona, err := deps.Persona.Get(ctx, tenantID, ownerUID, personaID)
	if err != nil {
		return err
	}

	research, err := deps.Placement.ResearchSettings(ctx, tenantID, ownerUID)
	if err != nil {
		return err
	}
	expandStrategy := placement.EffectiveExpandStrategy(research)
	if reqStrategy := strings.TrimSpace(stringField(payload, "expand_strategy")); reqStrategy != "" {
		expandStrategy = libkg.ParseExpandStrategy(reqStrategy)
	}
	if supplemental && placement.WebSearchAvailable(research) {
		expandStrategy = libkg.ExpandStrategyBrave
	}
	memberCtx, err := deps.ThreadsAccount.ResolveMemberPlacementContext(ctx, tenantID, ownerUID, research)
	if err != nil {
		return err
	}
	webClient := websearch.New(memberCtx.WebSearchConfig())

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

	entityMap := summaryResearchMap(mission.ResearchMap)
	missionCtx := libcopy.MissionContext{
		Label:       mission.Label,
		SeedQuery:   mission.SeedQuery,
		Brief:       mission.Brief,
		ResearchMap: entityMap,
	}
	personaCtx := libcopy.PersonaContext{
		DisplayName: persona.DisplayName,
		Persona:     persona.Persona,
	}

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

	var existing *kgusecase.GraphSummary
	if supplemental {
		existing, _ = deps.KnowledgeGraph.GetByCopyMission(ctx, tenantID, ownerUID, missionID)
	}

	braveSources := []libkg.BraveSource{}
	var systemPrompt string
	var userPrompt string

	switch expandStrategy {
	case libkg.ExpandStrategyLLM:
		updateProgress("整理延伸知識…", 25)
		systemPrompt, err = libprompt.KnowledgeGraphLLMSystem()
		if err != nil {
			return app.For(code.AI).SysInternal("knowledge graph llm prompt load failed")
		}
		kgVars := map[string]string{
			"seed":                   seed,
			"product_brief_line":     libkg.OptionalPromptLine("任務補充", mission.Brief),
			"target_audience_line":   libkg.OptionalPromptLine("目標受眾", mission.ResearchMap.AudienceSummary),
			"persona_line":           libkg.OptionalPromptLine("人設", persona.Persona),
			"research_pillars_line":  libkg.BulletPromptLine("內容支柱", mission.ResearchMap.Pillars),
			"research_questions_line": libkg.BulletPromptLine("受眾提問", mission.ResearchMap.Questions),
		}
		userPrompt, err = libprompt.KnowledgeGraphLLMUser(kgVars)
		if err != nil {
			return app.For(code.AI).SysInternal("knowledge graph llm user prompt load failed")
		}
	default:
		updateProgress("蒐集參考資料…", 10)
		l1Labels := []string{}
		if existing != nil {
			l1Labels = libkg.L1LabelsFromNodes(existing.Nodes)
		}
		planIn := libcopy.PlanInput(missionCtx, seed, l1Labels, supplemental, expandStrategy)
		queries := libkg.PlanQueries(planIn)
		updateProgress(fmt.Sprintf("蒐集參考資料（%d 項查詢）…", len(queries)), 25)
		braveSources, err = runWebKnowledgeExpand(ctx, webClient, memberCtx, queries, expandStrategy, func(i, total int) {
			pct := 25 + ((i + 1) * 30 / max(total, 1))
			updateProgress(fmt.Sprintf("蒐集參考資料 %d/%d…", i+1, total), pct)
		}, func() error {
			cancelled, _ := deps.Jobs.IsCancelRequested(ctx, step.JobID)
			if cancelled {
				return errJobCancelled
			}
			return ctx.Err()
		})
		if err != nil {
			return err
		}
		updateProgress("整理延伸知識…", 60)
		systemPrompt, err = libprompt.KnowledgeGraphSystem()
		if err != nil {
			return app.For(code.AI).SysInternal("knowledge graph prompt load failed")
		}
		userPrompt, err = libkg.BuildUserPrompt(libcopy.SynthInput(missionCtx, personaCtx, braveSources))
		if err != nil {
			return app.For(code.AI).SysInternal("knowledge graph user prompt load failed")
		}
	}

	genReq := placement.ResearchGenerateRequest(domai.GenerateRequest{
		Provider: providerID,
		Model:    credential.Model,
		Credential: domai.Credential{
			APIKey: credential.APIKey,
		},
		System: systemPrompt,
		Messages: []domai.Message{
			{Role: "user", Content: userPrompt},
		},
	})
	result, err := deps.AI.GenerateText(ctx, genReq)
	if err != nil {
		return err
	}

	graph, err := libkg.ParseSynthOutput(result.Text, libkg.SynthInput{
		Seed:           seed,
		TargetAudience: mission.ResearchMap.AudienceSummary,
	}, braveSources)
	if err != nil {
		return app.For(code.AI).SvcThirdParty("延伸知識產生失敗，請重試：" + err.Error())
	}
	if libkg.GraphTooThin(graph) {
		retryReq := placement.ResearchGenerateRequest(domai.GenerateRequest{
			Provider: providerID,
			Model:    credential.Model,
			Credential: domai.Credential{
				APIKey: credential.APIKey,
			},
			System: systemPrompt,
			Messages: []domai.Message{
				{Role: "user", Content: userPrompt},
				{Role: "assistant", Content: result.Text},
				{Role: "user", Content: libkg.KnowledgeGraphRetryUserPrompt()},
			},
		})
		if retryResult, retryErr := deps.AI.GenerateText(ctx, retryReq); retryErr == nil {
			if retryGraph, parseErr := libkg.ParseSynthOutput(retryResult.Text, libkg.SynthInput{
				Seed:           seed,
				TargetAudience: mission.ResearchMap.AudienceSummary,
			}, braveSources); parseErr == nil && !libkg.GraphTooThin(retryGraph) {
				graph = retryGraph
			}
		}
	}

	if supplemental && existing != nil {
		graph = mergeGraphs(existing, graph, braveSources)
	}
	if libkg.GraphNeedsBootstrap(graph) {
		libkg.SupplementGraphFromResearchMap(&graph, seed, mission.ResearchMap.Pillars, mission.ResearchMap.Questions)
	}
	preserveSelection := map[string]bool{}
	if existing != nil {
		for _, node := range existing.Nodes {
			preserveSelection[node.ID] = node.SelectedForScan
		}
	}
	libcopy.ApplyDefaultNodeSelectionPreserving(graph.Nodes, preserveSelection)
	libkg.DeriveSearchTagsFromGraph(&graph, libcopy.PatrolTagInput(missionCtx))

	updateProgress("儲存延伸知識…", 90)
	graph.BraveSources = braveSources
	now := clock.NowUnixNano()
	saved, err := deps.KnowledgeGraph.Upsert(ctx, kgusecase.UpsertRequest{
		TenantID:       tenantID,
		OwnerUID:       ownerUID,
		BrandID:        personaID,
		CopyMissionID:  missionID,
		Seed:           graph.Seed,
		Nodes:          graph.Nodes,
		Edges:          graph.Edges,
		BraveSources:   graph.BraveSources,
		ExpandStrategy: expandStrategy.String(),
		PainTagCount:   graph.PainTagCount,
		GeneratedAt:    now,
	})
	if err != nil {
		return err
	}
	if err := syncCopyMissionKnowledgeFromGraph(ctx, deps, tenantID, ownerUID, personaID, missionID, entityMap, saved.Nodes, braveSources); err != nil {
		return err
	}

	handoff := map[string]any{
		"flow":            "copy",
		"persona_id":      personaID,
		"copy_mission_id": missionID,
		"summary":         fmt.Sprintf("延伸知識 %d 節點，痛點候選 %d", len(saved.Nodes), saved.PainTagCount),
		"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{
			"graph_id":       saved.ID,
			"seed":           saved.Seed,
			"pain_tag_count": saved.PainTagCount,
			"node_count":     len(saved.Nodes),
			"handoff":        handoff,
		},
	})
	return err
}

func summaryResearchMap(summary missiondomain.ResearchMapSummary) missionentity.ResearchMap {
	tags := make([]missionentity.SuggestedTag, 0, len(summary.SuggestedTags))
	for _, tag := range summary.SuggestedTags {
		tags = append(tags, missionentity.SuggestedTag{
			Tag:          tag.Tag,
			Reason:       tag.Reason,
			SearchIntent: tag.SearchIntent,
			SearchType:   tag.SearchType,
		})
	}
	accounts := make([]missionentity.SimilarAccount, 0, len(summary.SimilarAccounts))
	for _, acc := range summary.SimilarAccounts {
		accounts = append(accounts, missionentity.SimilarAccount{
			Username:        acc.Username,
			Reason:          acc.Reason,
			Source:          acc.Source,
			MatchedSource:   append([]string(nil), acc.MatchedSource...),
			Confidence:      acc.Confidence,
			Status:          acc.Status,
			TopicRelevance:  acc.TopicRelevance,
			LastSeenAt:      acc.LastSeenAt,
			ProfileURL:      acc.ProfileURL,
			AuthorVerified:  acc.AuthorVerified,
			FollowerCount:   acc.FollowerCount,
			EngagementScore: acc.EngagementScore,
			LikeCount:       acc.LikeCount,
			ReplyCount:      acc.ReplyCount,
			PostCount:       acc.PostCount,
		})
	}
	samples := make([]missionentity.AudienceSample, 0, len(summary.AudienceSamples))
	for _, sample := range summary.AudienceSamples {
		samples = append(samples, missionentity.AudienceSample{
			Username:       sample.Username,
			SamplePostID:   sample.SamplePostID,
			SampleText:     sample.SampleText,
			ReplyLikeCount: sample.ReplyLikeCount,
			Appearances:    sample.Appearances,
			FirstSeenAt:    sample.FirstSeenAt,
			LastSeenAt:     sample.LastSeenAt,
			Status:         sample.Status,
		})
	}
	return missionentity.ResearchMap{
		AudienceSummary:        summary.AudienceSummary,
		ContentGoal:            summary.ContentGoal,
		Questions:              append([]string(nil), summary.Questions...),
		Pillars:                append([]string(nil), summary.Pillars...),
		Exclusions:             append([]string(nil), summary.Exclusions...),
		KnowledgeItems:         append([]string(nil), summary.KnowledgeItems...),
		SelectedKnowledgeItems: append([]string(nil), summary.SelectedKnowledgeItems...),
		SuggestedTags:          tags,
		SimilarAccounts:        accounts,
		AudienceSamples:        samples,
		BenchmarkNotes:         summary.BenchmarkNotes,
	}
}

func syncCopyMissionKnowledgeFromGraph(
	ctx context.Context,
	deps ExpandCopyMissionGraphDeps,
	tenantID, ownerUID, personaID, missionID string,
	prev missionentity.ResearchMap,
	nodes []libkg.Node,
	sources []libkg.BraveSource,
) error {
	researchMap := libcopy.BuildResearchMapFromGraph(prev, nodes, sources)
	_, err := deps.CopyMission.Update(ctx, missiondomain.UpdateRequest{
		TenantID:  tenantID,
		OwnerUID:  ownerUID,
		PersonaID: personaID,
		MissionID: missionID,
		Patch: missiondomain.MissionPatch{
			ResearchMap: &researchMap,
		},
	})
	return err
}