package app

import (
	"context"
	"errors"
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

type successfulRunner struct{}

func (successfulRunner) Run(_ context.Context, _ Job) (RunResult, error) {
	return RunResult{Message: "diagnosis", ThreadID: "thread-1", RepositoryBaseSHA: "base-sha"}, nil
}

type failingRunner struct{}

func (failingRunner) Run(_ context.Context, _ Job) (RunResult, error) {
	return RunResult{Message: "partial investigation", WorktreePath: "/tmp/retained", RepositoryBaseSHA: "failed-base-sha"}, errors.New("candidate tests failed")
}

type recordingRunner struct {
	jobs chan Job
}

func (r recordingRunner) Run(_ context.Context, job Job) (RunResult, error) {
	r.jobs <- job
	return RunResult{Message: "continued", ThreadID: job.ResumeThreadID}, nil
}

func TestWorkerPersistsSuccessAndNotifies(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	notifier := &recordingNotifier{}
	worker := NewWorker(store, successfulRunner{}, notifier, time.Second, time.Second, "")
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	worker.Start(ctx)

	job, inserted, err := worker.Enqueue(Job{MessageID: "om_worker", Request: "diagnose", Mode: JobModeAnalyze})
	require.NoError(t, err)
	require.True(t, inserted)
	require.Eventually(t, func() bool {
		actual, ok := store.Get(job.ID)
		return ok && actual.Status == JobSucceeded
	}, time.Second, 10*time.Millisecond)
	actual, _ := store.Get(job.ID)
	require.Equal(t, "thread-1", actual.ThreadID)
	require.Equal(t, "base-sha", actual.RepositoryBaseSHA)
	require.Equal(t, "diagnosis", actual.Result)
	require.Eventually(t, func() bool {
		notifier.mu.Lock()
		defer notifier.mu.Unlock()
		return len(notifier.replies) == 1
	}, time.Second, 10*time.Millisecond)
}

func TestWorkerResumesLatestSuccessfulAnalyzeJobInSameLarkThread(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	previous, _, err := store.Enqueue(Job{
		MessageID: "om_previous", ChatID: "oc_1", LarkRootMessageID: "om_root", Mode: JobModeAnalyze,
	})
	require.NoError(t, err)
	_, err = store.Update(previous.ID, func(job *Job) {
		job.Status = JobSucceeded
		job.ThreadID = "thread-previous"
	})
	require.NoError(t, err)

	runs := make(chan Job, 1)
	worker := NewWorker(store, recordingRunner{jobs: runs}, &recordingNotifier{}, time.Second, time.Second, "")
	current, _, err := worker.Enqueue(Job{
		MessageID: "om_follow_up", ChatID: "oc_1", LarkRootMessageID: "om_root", Mode: JobModeAnalyze,
	})
	require.NoError(t, err)
	worker.run(context.Background(), current.ID)

	run := <-runs
	require.Equal(t, "thread-previous", run.ResumeThreadID)
	require.Equal(t, previous.ID, run.ResumeJobID)
	stored, ok := store.Get(current.ID)
	require.True(t, ok)
	require.Equal(t, previous.ID, stored.ResumeJobID)
}

func TestWorkerSendsAutomaticSingleTicketSuccessToConfiguredChat(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	notifier := &recordingNotifier{}
	worker := NewWorker(store, successfulRunner{}, notifier, time.Second, time.Second, "oc_notify")
	job, _, err := worker.Enqueue(Job{
		MessageID: "auto-feedback:42", ChatID: "oc_stale", IssueKey: "APID-29232",
		Mode: JobModeTicket, Trigger: JobTriggerFeedback, FeedbackID: 42, FeedbackAction: "wrong",
	})
	require.NoError(t, err)

	worker.run(context.Background(), job.ID)
	worker.retryPendingNotifications(context.Background())
	actual, ok := store.Get(job.ID)
	require.True(t, ok)
	require.Equal(t, JobSucceeded, actual.Status)
	require.Equal(t, NotificationSent, actual.NotificationStatus)
	require.NotNil(t, actual.NotifiedAt)
	require.Len(t, notifier.sends, 1)
	require.Equal(t, []string{"oc_notify"}, notifier.sendChatIDs)
	require.Empty(t, notifier.replies)
	require.Contains(t, notifier.sends[0], "APID-29232")
	require.Contains(t, notifier.sends[0], "调查完成")
}

func TestWorkerRepliesToManualSingleTicketThread(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	notifier := &recordingNotifier{}
	worker := NewWorker(store, successfulRunner{}, notifier, time.Second, time.Second, "")
	job, _, err := worker.Enqueue(Job{
		MessageID: "om_ticket", ChatID: "oc_origin", IssueKey: "APID-29232",
		Mode: JobModeTicket, Trigger: JobTriggerLark,
	})
	require.NoError(t, err)

	worker.run(context.Background(), job.ID)
	worker.retryPendingNotifications(context.Background())
	actual, ok := store.Get(job.ID)
	require.True(t, ok)
	require.Equal(t, NotificationSent, actual.NotificationStatus)
	require.Len(t, notifier.replies, 1)
	require.Empty(t, notifier.sends)
}

func TestWorkerSendsAutomaticSingleTicketFailure(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	notifier := &recordingNotifier{}
	worker := NewWorker(store, failingRunner{}, notifier, time.Second, time.Second, "oc_notify")
	job, _, err := worker.Enqueue(Job{
		MessageID: "auto-feedback:43", ChatID: "oc_notify", IssueKey: "APID-29265",
		Mode: JobModeTicket, Trigger: JobTriggerFeedback, FeedbackID: 43, FeedbackAction: "revise",
	})
	require.NoError(t, err)

	worker.run(context.Background(), job.ID)
	worker.retryPendingNotifications(context.Background())
	actual, ok := store.Get(job.ID)
	require.True(t, ok)
	require.Equal(t, JobFailed, actual.Status)
	require.Equal(t, "failed-base-sha", actual.RepositoryBaseSHA)
	require.Equal(t, NotificationSent, actual.NotificationStatus)
	require.Len(t, notifier.sends, 1)
	require.Contains(t, notifier.sends[0], "🚨 单工单优化问题：执行失败")
	require.Contains(t, notifier.sends[0], "技术详情和排查现场已保留")
	require.NotContains(t, notifier.sends[0], "candidate tests failed")
}

func TestWorkerRetriesOnlyPendingNotificationWithoutRerunningJob(t *testing.T) {
	store, err := OpenStore(filepath.Join(t.TempDir(), "jobs.json"))
	require.NoError(t, err)
	notifier := &recordingNotifier{failSendCount: 1}
	worker := NewWorker(store, successfulRunner{}, notifier, time.Second, time.Second, "oc_notify")
	job, _, err := worker.Enqueue(Job{
		MessageID: "auto-feedback:44", ChatID: "oc_notify", IssueKey: "APID-29232",
		Mode: JobModeTicket, Trigger: JobTriggerFeedback, FeedbackID: 44, FeedbackAction: "wrong",
	})
	require.NoError(t, err)

	worker.run(context.Background(), job.ID)
	worker.retryPendingNotifications(context.Background())
	actual, ok := store.Get(job.ID)
	require.True(t, ok)
	require.Equal(t, JobSucceeded, actual.Status)
	require.Equal(t, NotificationPending, actual.NotificationStatus)
	require.Contains(t, actual.NotificationError, "temporary send failure")
	require.Empty(t, notifier.sends)

	worker.retryPendingNotifications(context.Background())
	actual, ok = store.Get(job.ID)
	require.True(t, ok)
	require.Equal(t, JobSucceeded, actual.Status)
	require.Equal(t, NotificationSent, actual.NotificationStatus)
	require.Empty(t, actual.NotificationError)
	require.Len(t, notifier.sends, 1)
}

func TestSingleTicketNotificationTranslatesFocusCandidateForHumans(t *testing.T) {
	investigation := focusInvestigation()
	investigation.ClassificationReason = "The chain completed, but the answer quality needs multiple cases."

	message := singleTicketNotification(Job{
		ID: "86965756ccc0fc2a", IssueKey: "TCID-13011", Status: JobSucceeded,
		FeedbackID: 94347, FeedbackAction: "wrong", Investigation: &investigation,
		Result: "raw internal result that must not be sent",
	})

	require.Contains(t, message, "单工单优化结果：需要结合更多工单评估")
	require.Contains(t, message, "工单：TCID-13011 https://tidb.atlassian.net/browse/TCID-13011")
	require.Contains(t, message, "反馈：人工认为回答有误（/ao wrong，#94347）")
	require.Contains(t, message, "当前答案 Judge：ai_better（different_answer_version）")
	require.Contains(t, message, "历史答案复核：wrong；历史反馈问题：reproduced")
	require.Contains(t, message, "执行链路正常，但答案质量或证据标准仍有问题")
	require.Contains(t, message, "暂不修改代码；保留为后续集中优化候选（Focus）")
	require.NotContains(t, message, "focus_candidate")
	require.NotContains(t, message, "requires_cross_case_evaluation")
	require.NotContains(t, message, "analysis_quality_gap")
	require.NotContains(t, message, "raw internal result")
	require.NotContains(t, message, "The chain completed")
}

func TestSingleTicketNotificationUsesConciseChineseInvestigationSummary(t *testing.T) {
	investigation := noProblemInvestigation()
	investigation.ClassificationReason = "当前版本已经不再复现反馈中的问题，无需修改代码。"

	message := singleTicketNotification(Job{
		ID: "job-1", IssueKey: "ST-2861", Status: JobSucceeded,
		FeedbackID: 94361, FeedbackAction: "revise", Investigation: &investigation,
	})

	require.Contains(t, message, "单工单优化结果：当前无需修复")
	require.Contains(t, message, "反馈：人工已改写回答（/ao revise，#94361）")
	require.Contains(t, message, "当前答案 Judge：ai_better（different_answer_version）")
	require.Contains(t, message, "历史答案复核：wrong；历史反馈问题：not_reproduced")
	require.Contains(t, message, investigation.ClassificationReason)
	require.NotContains(t, message, "no_problem")
	require.NotContains(t, message, "not_applicable")
}

func TestSingleTicketNotificationNamesInconclusiveReplayFailure(t *testing.T) {
	investigation := noProblemInvestigation()
	investigation.Classification = "inconclusive"
	investigation.ClassificationReason = "当前只读回放因时间截点无效而失败，无法完成当前版本复核。"
	investigation.Findings = []SingleTicketFinding{{
		ID:                  "replay-cutoff",
		Kind:                "orchestration_fallback_contract",
		FirstBrokenBoundary: &SingleTicketBrokenBoundary{Stage: "input_selection"},
		DiagnosticImpact:    SingleTicketImpact{Kind: "terminal_failure"},
		RepairDecision:      SingleTicketRepairDecision{Repairability: "requires_human_judgment"},
	}}

	message := singleTicketNotification(Job{
		ID: "49a4d40d2f3d00dc", IssueKey: "TCID-13127", Status: JobSucceeded,
		CandidateID: 986, CandidateSource: "runtime", CandidateSignal: "evidence_acquisition_degraded",
		Investigation: &investigation,
	})

	require.Contains(t, message, "🚨 单工单优化问题：回放失败")
	require.Contains(t, message, investigation.ClassificationReason)
	require.Contains(t, message, "回放故障已作为工程链路问题保留")
	require.NotContains(t, message, "单工单优化受阻：回放失败")
	require.NotContains(t, message, "单工单优化结果：回放失败")
	require.NotContains(t, message, "证据不足")
}

func TestSingleTicketNotificationNamesInconclusiveProviderFailure(t *testing.T) {
	investigation := noProblemInvestigation()
	investigation.Classification = "inconclusive"
	investigation.ClassificationReason = "当前证据查询失败，无法检查诊断结果。"
	investigation.Findings = []SingleTicketFinding{{
		ID:                  "provider-query",
		Kind:                "provider_query_contract",
		FirstBrokenBoundary: &SingleTicketBrokenBoundary{Stage: "provider_execution"},
		DiagnosticImpact:    SingleTicketImpact{Kind: "terminal_failure"},
		RepairDecision:      SingleTicketRepairDecision{Repairability: "requires_human_judgment"},
	}}

	message := singleTicketNotification(Job{
		ID: "job-provider", IssueKey: "TCID-13128", Status: JobSucceeded,
		Investigation: &investigation,
	})

	require.Contains(t, message, "🚨 单工单优化问题：证据查询失败")
	require.NotContains(t, message, "单工单优化受阻：证据查询失败")
	require.NotContains(t, message, "单工单优化结果：证据查询失败")
	require.NotContains(t, message, "证据不足")
}

func TestSingleTicketNotificationAlarmsOnPostDeployReplayFailure(t *testing.T) {
	message := singleTicketNotification(Job{
		ID: "job-post-deploy", IssueKey: "TCID-13127", Status: JobFailed,
		MergeSHA: "0123456789abcdef", PRURL: "https://github.com/pingcap/aisupport/pull/1300",
		Verification: "post-change replay failed",
	})

	require.Contains(t, message, "🚨 单工单优化问题：部署后验证失败")
	require.Contains(t, message, "报警：修复已部署，但部署后 replay 或收尾验证失败，当前问题仍未确认修复。")
	require.NotContains(t, message, "单工单优化结果：部署后验证")
}
