package app

import (
	"context"
	"fmt"
	"log/slog"
	"strings"
	"time"
	"unicode"
)

type Notifier interface {
	Reply(context.Context, string, string) error
	ReplyOnce(context.Context, string, string, string) error
	Send(context.Context, string, string, string) error
}

type Worker struct {
	store               *Store
	runner              AgentRunner
	notifier            Notifier
	timeout             time.Duration
	singleTicketTimeout time.Duration
	notificationChatID  string
	wakeup              chan struct{}
	notificationWakeup  chan struct{}
}

func NewWorker(store *Store, runner AgentRunner, notifier Notifier, timeout, singleTicketTimeout time.Duration, notificationChatID string) *Worker {
	return &Worker{
		store: store, runner: runner, notifier: notifier, timeout: timeout,
		singleTicketTimeout: singleTicketTimeout, notificationChatID: strings.TrimSpace(notificationChatID),
		wakeup: make(chan struct{}, 1), notificationWakeup: make(chan struct{}, 1),
	}
}

func (w *Worker) Start(ctx context.Context) {
	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case <-w.wakeup:
				for _, id := range w.store.QueuedIDs() {
					w.run(ctx, id)
				}
			}
		}
	}()
	go func() {
		ticker := time.NewTicker(time.Minute)
		defer ticker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-w.notificationWakeup:
				w.retryPendingNotifications(ctx)
			case <-ticker.C:
				w.retryPendingNotifications(ctx)
			}
		}
	}()
	w.signal()
	w.signalNotification()
}

func (w *Worker) Enqueue(job Job) (Job, bool, error) {
	stored, inserted, err := w.store.Enqueue(job)
	if err == nil && inserted {
		w.signal()
	}
	return stored, inserted, err
}

func (w *Worker) signal() {
	select {
	case w.wakeup <- struct{}{}:
	default:
	}
}

func (w *Worker) signalNotification() {
	select {
	case w.notificationWakeup <- struct{}{}:
	default:
	}
}

func (w *Worker) run(parent context.Context, id string) {
	job, err := w.store.Update(id, func(job *Job) {
		job.Status = JobRunning
		job.Error = ""
	})
	if err != nil {
		slog.Error("claim optimizer job", "job_id", id, "error", err)
		return
	}
	if job.Mode == JobModeAnalyze && job.LarkRootMessageID != "" {
		if previous, ok := w.store.LatestSuccessfulAnalyzeThread(job.ChatID, job.LarkRootMessageID, job.ID); ok {
			job, err = w.store.Update(id, func(job *Job) {
				job.ResumeThreadID = previous.ThreadID
				job.ResumeJobID = previous.ID
			})
			if err != nil {
				slog.Error("record optimizer resume source", "job_id", id, "error", err)
				return
			}
		}
	}
	timeout := w.timeout
	if job.Mode == JobModeTicket {
		timeout = w.singleTicketTimeout
	}
	ctx, cancel := context.WithTimeout(parent, timeout)
	defer cancel()
	result, runErr := w.runner.Run(ctx, job)
	if runErr != nil {
		failed, updateErr := w.store.Update(id, func(job *Job) {
			job.Status = JobFailed
			job.ThreadID = result.ThreadID
			job.WorktreePath = result.WorktreePath
			job.RepositoryBaseSHA = result.RepositoryBaseSHA
			job.Result = truncate(result.Message, 8000)
			job.PRURL = result.PRURL
			job.VerificationPRURL = result.VerificationPRURL
			job.MergeSHA = result.MergeSHA
			job.VerificationMergeSHA = result.VerificationMergeSHA
			job.ReplayRunID = result.ReplayRunID
			job.Verification = result.Verification
			job.Investigation = result.Investigation
			job.Error = truncate(runErr.Error(), 2000)
			if job.Mode == JobModeTicket {
				job.NotificationStatus = NotificationPending
				job.NotificationError = ""
				job.NotifiedAt = nil
			}
		})
		if updateErr != nil {
			slog.Error("record optimizer failure", "job_id", id, "error", updateErr)
			return
		}
		if failed.Mode == JobModeTicket {
			w.signalNotification()
		} else {
			detail := fmt.Sprintf("任务 `%s` 失败：%s\n保留工作目录：%s", id, failed.Error, failed.WorktreePath)
			if failed.PRURL != "" {
				detail += "\nPR 已创建但清理未完成：" + failed.PRURL
			}
			_ = w.notifier.Reply(context.WithoutCancel(parent), failed.MessageID, detail)
		}
		return
	}
	succeeded, err := w.store.Update(id, func(job *Job) {
		job.Status = JobSucceeded
		job.ThreadID = result.ThreadID
		job.WorktreePath = result.WorktreePath
		job.RepositoryBaseSHA = result.RepositoryBaseSHA
		job.Result = truncate(result.Message, 12000)
		job.PRURL = result.PRURL
		job.VerificationPRURL = result.VerificationPRURL
		job.MergeSHA = result.MergeSHA
		job.VerificationMergeSHA = result.VerificationMergeSHA
		job.ReplayRunID = result.ReplayRunID
		job.Verification = result.Verification
		job.Investigation = result.Investigation
		job.Error = ""
		if job.Mode == JobModeTicket {
			job.NotificationStatus = NotificationPending
			job.NotificationError = ""
			job.NotifiedAt = nil
		}
	})
	if err != nil {
		slog.Error("record optimizer success", "job_id", id, "error", err)
		return
	}
	if succeeded.Mode == JobModeTicket {
		w.signalNotification()
		return
	}
	reply := succeeded.Result
	if succeeded.PRURL != "" {
		reply = fmt.Sprintf("任务 `%s` 已提交 PR：%s\n\n%s", id, succeeded.PRURL, reply)
	} else {
		reply = fmt.Sprintf("任务 `%s` 分析完成：\n\n%s", id, reply)
	}
	if err := w.notifier.Reply(context.WithoutCancel(parent), succeeded.MessageID, truncate(reply, 15000)); err != nil {
		slog.Error("send optimizer result", "job_id", id, "error", err)
	}
}

func (w *Worker) retryPendingNotifications(parent context.Context) {
	for _, id := range w.store.PendingNotificationIDs() {
		w.notifyTicketOutcome(parent, id)
	}
}

func (w *Worker) notifyTicketOutcome(parent context.Context, id string) {
	job, ok := w.store.Get(id)
	if !ok || job.Mode != JobModeTicket || job.NotificationStatus != NotificationPending {
		return
	}
	message := singleTicketNotification(job)
	ctx := context.WithoutCancel(parent)
	var err error
	idempotencyKey := "single-ticket-" + job.ID
	if job.Trigger == JobTriggerFeedback || job.Trigger == JobTriggerCandidate {
		chatID := w.notificationChatID
		if chatID == "" {
			chatID = job.ChatID
		}
		err = w.notifier.Send(ctx, chatID, idempotencyKey, message)
	} else {
		err = w.notifier.ReplyOnce(ctx, job.MessageID, idempotencyKey, message)
	}
	if err != nil {
		_, updateErr := w.store.Update(id, func(job *Job) {
			job.NotificationError = truncate(err.Error(), 2000)
		})
		if updateErr != nil {
			slog.Error("record optimizer notification failure", "job_id", id, "error", updateErr)
		}
		slog.Error("send single-ticket optimizer result", "job_id", id, "error", err)
		return
	}
	now := time.Now().UTC()
	if _, err := w.store.Update(id, func(job *Job) {
		job.NotificationStatus = NotificationSent
		job.NotificationError = ""
		job.NotifiedAt = &now
	}); err != nil {
		slog.Error("record optimizer notification success", "job_id", id, "error", err)
	}
}

func singleTicketNotification(job Job) string {
	ticketLine := fmt.Sprintf("工单：%s https://tidb.atlassian.net/browse/%s", job.IssueKey, job.IssueKey)
	feedbackLine := ""
	if job.FeedbackID > 0 {
		feedbackLine = fmt.Sprintf("反馈：%s（/ao %s，#%d）", feedbackActionLabel(job.FeedbackAction), job.FeedbackAction, job.FeedbackID)
	} else if job.CandidateID > 0 {
		feedbackLine = fmt.Sprintf("信号：%s / %s（candidate #%d）", job.CandidateSource, job.CandidateSignal, job.CandidateID)
	}
	if job.Status == JobFailed {
		title := "执行失败"
		conclusion := "任务在调查或验证阶段失败，系统没有自动修改代码或创建新的修复 PR。"
		action := "技术详情已保留，可按任务 ID 继续检查。"
		alert := ""
		if job.WorktreePath != "" {
			action = "技术详情和排查现场已保留，可按任务 ID 继续检查。"
		}
		if job.MergeSHA != "" {
			title = "部署后验证失败"
			conclusion = "修复已经合并并部署，但 post-change replay、Jira 回写或优化状态更新未完整通过。"
			action = "同 owner 的局部问题只会自动续修一次；当前已停止扩大修改并通知人工处理。"
			alert = "报警：修复已部署，但部署后 replay 或收尾验证失败，当前问题仍未确认修复。"
		}
		lines := []string{"🚨 单工单优化问题：" + title, ticketLine}
		if feedbackLine != "" {
			lines = append(lines, feedbackLine)
		}
		if alert != "" {
			lines = append(lines, alert)
		}
		lines = append(lines, "结论："+conclusion, "处理："+action)
		if job.PRURL != "" {
			lines = append(lines, "修复 PR："+job.PRURL)
		}
		if job.Verification != "" {
			lines = append(lines, "验证："+job.Verification)
		}
		lines = append(lines, "任务 ID："+job.ID)
		return truncate(strings.Join(lines, "\n"), 15000)
	}
	title, conclusion, action := singleTicketHumanOutcome(job)
	lines := []string{singleTicketNotificationHeadline(job, title), ticketLine}
	if feedbackLine != "" {
		lines = append(lines, feedbackLine)
	}
	lines = append(lines, singleTicketAnswerOutcomeLines(job.Investigation)...)
	lines = append(lines, "结论："+conclusion, "处理："+action)
	if job.PRURL != "" {
		lines = append(lines, "修复 PR："+job.PRURL)
	}
	if job.VerificationPRURL != "" {
		lines = append(lines, "状态 PR："+job.VerificationPRURL)
	}
	if job.ReplayRunID != "" {
		lines = append(lines, "验证 replay："+job.ReplayRunID)
	}
	lines = append(lines, "任务 ID："+job.ID)
	return truncate(strings.Join(lines, "\n"), 15000)
}

func singleTicketNotificationHeadline(job Job, title string) string {
	if job.Investigation != nil && job.Investigation.Classification == "inconclusive" {
		if _, _, _, ok := inconclusiveFailureOutcome(job); ok {
			return "🚨 单工单优化问题：" + title
		}
	}
	return "单工单优化结果：" + title
}

func singleTicketAnswerOutcomeLines(investigation *SingleTicketInvestigation) []string {
	if investigation == nil || !validAnswerVersion(investigation.AnswerVersion) {
		return nil
	}
	return []string{
		fmt.Sprintf("当前答案 Judge：%s（%s）", investigation.AnswerVersion.CurrentJudgeVerdict, investigation.AnswerVersion.JudgeAnswerScope),
		fmt.Sprintf("历史答案复核：%s；历史反馈问题：%s", investigation.AnswerReview.NominatedAnswerVerdict, investigation.AnswerReview.NominatedProblemStatus),
	}
}

func feedbackActionLabel(action string) string {
	if action == "revise" {
		return "人工已改写回答"
	}
	return "人工认为回答有误"
}

func singleTicketHumanOutcome(job Job) (string, string, string) {
	if job.PRURL != "" {
		if job.ReplayRunID != "" && job.VerificationMergeSHA != "" {
			return "优化成功", investigationSummary(job.Investigation, "已确认并修复一个可复现、可确定验收的工程问题。"), "修复和状态记录均已自动合并部署；replay、Jira 内部评论与 optimized_verified 已完成。"
		}
		return "已创建修复 PR", investigationSummary(job.Investigation, "已确认一个可复现、可确定验收的工程问题。"), "修复 PR 已创建，自动合并或部署验证尚未完整结束。"
	}
	if job.Investigation == nil {
		return "调查完成", "任务已完成，详细结构化结果已保留在任务记录中。", "没有创建代码修改或 PR。"
	}
	switch job.Investigation.Classification {
	case "focus_candidate":
		if investigationRequiresHumanJudgment(job.Investigation) {
			return "需要人工判断", investigationSummary(job.Investigation, "执行链路正常，但回答标准需要人工确认，不能由单个工单自动决定。"), "暂不修改代码；保留该问题供人工确认或后续集中优化。"
		}
		return "需要结合更多工单评估", investigationSummary(job.Investigation, "执行链路正常，但答案质量或证据标准仍有问题；单个工单不能证明一个可确定修复的工程缺陷。"), "暂不修改代码；保留为后续集中优化候选（Focus）。"
	case "no_problem":
		return "当前无需修复", investigationSummary(job.Investigation, "当前版本未复现反馈问题，或问题已经在现有版本中解决。"), "不修改代码，也不创建 PR。"
	case "inconclusive":
		if title, conclusion, action, ok := inconclusiveFailureOutcome(job); ok {
			return title, conclusion, action
		}
		return "问题未定位", investigationSummary(job.Investigation, "现有上下文不足，暂时无法确认具体问题。"), "不自动修改代码；需要补充上下文后再判断。"
	case "engineering_defect":
		return "发现问题但未自动修复", investigationSummary(job.Investigation, "调查发现了工程链路问题，但当前证据不满足自动修复条件。"), "未创建代码修改；需要补充确定性验收条件或人工处理。"
	default:
		return "调查完成", "调查已经结束，详细结构化结果已保留在任务记录中。", "没有创建代码修改或 PR。"
	}
}

func inconclusiveFailureOutcome(job Job) (string, string, string, bool) {
	if job.Investigation == nil {
		return "", "", "", false
	}
	for _, finding := range job.Investigation.Findings {
		if finding.Kind == "historical_only" || finding.FirstBrokenBoundary == nil || finding.DiagnosticImpact.Kind != "terminal_failure" {
			continue
		}
		switch finding.FirstBrokenBoundary.Stage {
		case "provider_execution":
			return "证据查询失败",
				investigationSummary(job.Investigation, "本次证据查询失败，无法完成当前版本复核。"),
				"未修改工单回答；先处理查询链路，再重跑本工单。", true
		case "input_selection", "context_materialization", "routing", "evaluation", "runtime_completion":
			return "回放失败",
				investigationSummary(job.Investigation, "本次只读回放在生成或评测回答前失败，无法完成当前版本复核。"),
				"未修改工单回答；回放故障已作为工程链路问题保留，按任务 ID 排查。", true
		}
	}
	return "", "", "", false
}

func investigationRequiresHumanJudgment(investigation *SingleTicketInvestigation) bool {
	if investigation == nil {
		return false
	}
	for _, finding := range investigation.Findings {
		if finding.RepairDecision.Repairability == "requires_human_judgment" {
			return true
		}
	}
	return investigation.RepairDecision != nil && investigation.RepairDecision.Repairability == "requires_human_judgment"
}

func investigationSummary(investigation *SingleTicketInvestigation, fallback string) string {
	if investigation == nil {
		return fallback
	}
	summary := strings.TrimSpace(investigation.ClassificationReason)
	for _, char := range summary {
		if unicode.Is(unicode.Han, char) {
			return summary
		}
	}
	return fallback
}

func truncate(value string, limit int) string {
	value = strings.TrimSpace(value)
	if len([]rune(value)) <= limit {
		return value
	}
	return string([]rune(value)[:limit]) + "…"
}
