package app

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"log/slog"
	"regexp"
	"strings"
	"time"

	lark "github.com/larksuite/oapi-sdk-go/v3"
	"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
	larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
	"github.com/larksuite/oapi-sdk-go/v3/ws"
)

type LarkMessenger struct {
	client *lark.Client
}

var larkOpenIDPattern = regexp.MustCompile(`^ou_[A-Za-z0-9_-]+$`)

func NewLarkMessenger(appID, appSecret string) *LarkMessenger {
	return &LarkMessenger{client: lark.NewClient(appID, appSecret)}
}

func (m *LarkMessenger) Reply(ctx context.Context, messageID, text string) error {
	return m.reply(ctx, messageID, "", text)
}

func (m *LarkMessenger) ReplyOnce(ctx context.Context, messageID, idempotencyKey, text string) error {
	return m.reply(ctx, messageID, idempotencyKey, text)
}

func (m *LarkMessenger) reply(ctx context.Context, messageID, idempotencyKey, text string) error {
	content, err := json.Marshal(map[string]string{"text": text})
	if err != nil {
		return fmt.Errorf("encode Lark reply: %w", err)
	}
	body := larkim.NewReplyMessageReqBodyBuilder().
		MsgType("text").
		Content(string(content)).
		ReplyInThread(true)
	if idempotencyKey != "" {
		body.Uuid(idempotencyKey)
	}
	req := larkim.NewReplyMessageReqBuilder().
		MessageId(messageID).
		Body(body.Build()).
		Build()
	resp, err := m.client.Im.Message.Reply(ctx, req)
	if err != nil {
		return fmt.Errorf("reply Lark message: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("reply Lark message: %s", resp.CodeError.String())
	}
	return nil
}

func (m *LarkMessenger) Send(ctx context.Context, chatID, idempotencyKey, text string) error {
	content, err := json.Marshal(map[string]string{"text": text})
	if err != nil {
		return fmt.Errorf("encode Lark message: %w", err)
	}
	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType("chat_id").
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(chatID).
			MsgType("text").
			Content(string(content)).
			Uuid(idempotencyKey).
			Build()).
		Build()
	resp, err := m.client.Im.Message.Create(ctx, req)
	if err != nil {
		return fmt.Errorf("send Lark message: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("send Lark message: %s", resp.CodeError.String())
	}
	return nil
}

func (m *LarkMessenger) SendFeedbackReminder(ctx context.Context, chatID, idempotencyKey string, groups []FeedbackReminderGroup) error {
	post, err := feedbackReminderPostContent(groups)
	if err != nil {
		return err
	}
	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType("chat_id").
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(chatID).
			MsgType("post").
			Content(post).
			Uuid(idempotencyKey).
			Build()).
		Build()
	resp, err := m.client.Im.Message.Create(ctx, req)
	if err != nil {
		return fmt.Errorf("send feedback reminder: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("send feedback reminder: %s", resp.CodeError.String())
	}
	return nil
}

func feedbackReminderPostContent(groups []FeedbackReminderGroup) (string, error) {
	content := make([][]map[string]any, 0, len(groups)+1)
	content = append(content, []map[string]any{{
		"tag":  "text",
		"text": "Please take a moment to submit /ao feedback:",
	}})
	for _, group := range groups {
		if !larkOpenIDPattern.MatchString(group.OpenID) {
			return "", fmt.Errorf("invalid Lark open_id for %s", group.DisplayName)
		}
		line := []map[string]any{
			{"tag": "at", "user_id": group.OpenID, "user_name": group.DisplayName},
			{"tag": "text", "text": " - "},
		}
		for index, ticket := range group.Tickets {
			if index > 0 {
				line = append(line, map[string]any{"tag": "text", "text": ", "})
			}
			line = append(line, map[string]any{"tag": "a", "href": ticket.JiraURL, "text": ticket.IssueKey})
		}
		content = append(content, line)
	}
	post, err := json.Marshal(map[string]any{
		"en_us": map[string]any{
			"title":   "AI Feedback Reminder",
			"content": content,
		},
	})
	if err != nil {
		return "", fmt.Errorf("encode feedback reminder: %w", err)
	}
	return string(post), nil
}

func StartLarkLongConnection(ctx context.Context, config Config, ingress *LarkIngress) error {
	handler := dispatcher.NewEventDispatcher("", "").OnP2MessageReceiveV1(ingress.Handle)
	return ws.NewClient(config.LarkAppID, config.LarkAppSecret, ws.WithEventHandler(handler)).Start(ctx)
}

type LarkIngress struct {
	botOpenID      string
	allowedChats   map[string]struct{}
	allowedSenders map[string]struct{}
	prSenders      map[string]struct{}
	worker         *Worker
	messenger      Notifier
	reminder       FeedbackReminderRunner
	reminderChatID string
}

func NewLarkIngress(config Config, worker *Worker, messenger Notifier, reminders ...FeedbackReminderRunner) *LarkIngress {
	ingress := &LarkIngress{
		botOpenID:      config.LarkBotOpenID,
		allowedChats:   config.AllowedChatIDs,
		allowedSenders: config.AllowedSenderOpenIDs, prSenders: config.PRSenderOpenIDs,
		worker: worker, messenger: messenger, reminderChatID: config.FeedbackReminderChatID,
	}
	if len(reminders) > 0 {
		ingress.reminder = reminders[0]
	}
	return ingress
}

func (h *LarkIngress) Handle(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
	if event == nil || event.Event == nil || event.Event.Message == nil || event.Event.Sender == nil || event.Event.Sender.SenderId == nil {
		return fmt.Errorf("invalid Lark message event")
	}
	message := event.Event.Message
	if value(message.MessageType) != "text" {
		return nil
	}
	chatID := value(message.ChatId)
	senderID := value(event.Event.Sender.SenderId.OpenId)
	isP2P := value(message.ChatType) == "p2p"
	if isP2P {
		if _, ok := h.allowedSenders[senderID]; !ok {
			return h.messenger.Reply(ctx, value(message.MessageId), "请在群聊中 @Auto Oncall 使用；私聊仅对授权用户开放。")
		}
	} else {
		if _, ok := h.allowedChats[chatID]; !ok {
			return nil
		}
		if !mentionsBot(message.Mentions, h.botOpenID) {
			return nil
		}
	}
	text, err := decodeText(value(message.Content))
	if err != nil {
		return err
	}
	for _, mention := range message.Mentions {
		if mention != nil {
			text = strings.ReplaceAll(text, value(mention.Key), " ")
		}
	}
	if strings.TrimSpace(text) == "/fb-reminder" {
		if isP2P {
			return h.messenger.Reply(ctx, value(message.MessageId), "请在 Auto-Oncall Feedback Hub 群中 @Auto Oncall 使用 `/fb-reminder`。")
		}
		if h.reminder == nil || h.reminderChatID == "" {
			return h.messenger.Reply(ctx, value(message.MessageId), "AI feedback reminder 当前未启用。")
		}
		if chatID != h.reminderChatID {
			return h.messenger.Reply(ctx, value(message.MessageId), "请在 Auto-Oncall Feedback Hub 群中触发 `/fb-reminder`。")
		}
		messageID := value(message.MessageId)
		if err := h.messenger.Reply(ctx, messageID, "正在生成近 14 天缺少 `/ao` 反馈的工单提醒。"); err != nil {
			return err
		}
		go h.runFeedbackReminder(messageID)
		return nil
	}
	if fields := strings.Fields(strings.TrimSpace(text)); len(fields) > 0 && fields[0] == "/fb-reminder" {
		return h.messenger.Reply(ctx, value(message.MessageId), "命令格式：`/fb-reminder`，无需附加参数。")
	}
	mode, request, ok := ParseCommand(text)
	if !ok {
		return h.messenger.Reply(ctx, value(message.MessageId), "请描述要分析的问题；普通修改使用 `/pr <需求>`；单 ticket 优化使用 `/ticket <ISSUE-KEY> [问题描述]`。")
	}
	if (mode == JobModePR || mode == JobModeTicket) && isP2P {
		if _, ok := h.prSenders[senderID]; !ok {
			return h.messenger.Reply(ctx, value(message.MessageId), "你可以使用只读分析，但暂时没有提交 PR 的权限，也不能启动可能生成 Draft PR 的单 ticket 优化。")
		}
	}
	issueKey := ""
	if mode == JobModeTicket {
		var valid bool
		issueKey, _, valid = parseSingleTicketRequest(request)
		if !valid {
			return h.messenger.Reply(ctx, value(message.MessageId), "单 ticket 优化需要一个明确的 Jira issue key。")
		}
	}
	job, inserted, err := h.worker.Enqueue(Job{
		MessageID: value(message.MessageId), ChatID: chatID, LarkRootMessageID: larkRootMessageID(message, isP2P), SenderOpenID: senderID,
		Request: request, IssueKey: issueKey, Trigger: JobTriggerLark, Mode: mode,
	})
	if err != nil {
		return err
	}
	if !inserted {
		return h.messenger.Reply(ctx, job.MessageID, fmt.Sprintf("该消息已对应任务 `%s`，当前状态：%s。", job.ID, job.Status))
	}
	modeText := "只读分析"
	if mode == JobModePR {
		modeText = "修改并提交 PR"
	} else if mode == JobModeTicket {
		modeText = "单 ticket 调查；仅确定性工程问题会生成 Draft PR"
	}
	return h.messenger.Reply(ctx, job.MessageID, fmt.Sprintf("已创建任务 `%s`（%s），完成后会在此线程回复。", job.ID, modeText))
}

func (h *LarkIngress) runFeedbackReminder(messageID string) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	result, err := h.reminder.Run(ctx, manualFeedbackReminderUUID(messageID))
	cancel()
	replyCtx, replyCancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer replyCancel()
	if err != nil {
		slog.Error("run manual feedback reminder", "error", err)
		_ = h.messenger.ReplyOnce(replyCtx, messageID, manualFeedbackReminderResultUUID(messageID), "AI feedback reminder 生成失败，请查看 optimizer 日志。")
		return
	}
	if !result.Sent {
		_ = h.messenger.ReplyOnce(replyCtx, messageID, manualFeedbackReminderResultUUID(messageID), "当前没有需要提醒的工单。")
		return
	}
	_ = h.messenger.ReplyOnce(replyCtx, messageID, manualFeedbackReminderResultUUID(messageID), fmt.Sprintf("提醒已发送：%d 张工单，@ %d 位 Assignee。", result.TicketCount, result.AssigneeCount))
}

func manualFeedbackReminderUUID(messageID string) string {
	digest := sha256.Sum256([]byte(messageID))
	return fmt.Sprintf("fb-reminder-manual:%x", digest[:12])
}

func manualFeedbackReminderResultUUID(messageID string) string {
	digest := sha256.Sum256([]byte("result:" + messageID))
	return fmt.Sprintf("fb-reminder-result:%x", digest[:12])
}

func larkRootMessageID(message *larkim.EventMessage, isP2P bool) string {
	if message == nil || isP2P {
		return ""
	}
	if rootID := value(message.RootId); rootID != "" {
		return rootID
	}
	return value(message.MessageId)
}

func mentionsBot(mentions []*larkim.MentionEvent, botOpenID string) bool {
	for _, mention := range mentions {
		if mention != nil && mention.Id != nil && value(mention.Id.OpenId) == botOpenID {
			return true
		}
	}
	return false
}

func decodeText(content string) (string, error) {
	var payload struct {
		Text string `json:"text"`
	}
	if err := json.Unmarshal([]byte(content), &payload); err != nil {
		return "", fmt.Errorf("decode Lark text: %w", err)
	}
	return payload.Text, nil
}

func value(pointer *string) string {
	if pointer == nil {
		return ""
	}
	return *pointer
}
