package summary

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"regexp"
	"strings"

	"github.com/pardnchiu/agenvoy/configs"
	agentTypes "github.com/pardnchiu/agenvoy/internal/agents/types"
	sessionManager "github.com/pardnchiu/agenvoy/internal/session"
)

func extractMessageTime(msg agentTypes.Message) string {
	s, ok := msg.Content.(string)
	if !ok {
		return ""
	}
	m := regexp.MustCompile(`當前時間:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})`).FindStringSubmatch(s)
	if len(m) < 2 {
		return ""
	}
	return m[1]
}

func latestMessageTime(messages []agentTypes.Message) string {
	var latest string
	for _, m := range messages {
		t := extractMessageTime(m)
		if t > latest {
			latest = t
		}
	}
	return latest
}

func filterAfterTime(messages []agentTypes.Message, cursor string) []agentTypes.Message {
	if cursor == "" {
		return messages
	}
	out := make([]agentTypes.Message, 0, len(messages))
	for _, m := range messages {
		t := extractMessageTime(m)
		if t == "" || t > cursor {
			out = append(out, m)
		}
	}
	return out
}

func Generate(ctx context.Context, agent agentTypes.Agent, sessionID string, histories []agentTypes.Message) error {
	raw, summaryMap := sessionManager.EnsureSummary(sessionID)
	meta := sessionManager.GetSummaryMeta(sessionID)

	if meta.LastMessageTime == "" && len(summaryMap) > 0 {
		latest := latestMessageTime(histories)
		sessionManager.SaveSummaryMeta(sessionID, latest)
		sessionManager.SaveSummary(sessionID, summaryMap)
		return nil
	}

	newHistories := filterAfterTime(histories, meta.LastMessageTime)
	if len(newHistories) == 0 {
		return nil
	}

	oldSummary := string(raw)
	if oldSummary == "" {
		oldSummary = "{}"
	}

	chunks := chunkMessages(newHistories, 16)
	cursor := meta.LastMessageTime

	for i, chunk := range chunks {
		newMap := generatePass(ctx, agent, oldSummary, chunk)
		if newMap == nil {
			slog.Warn("summary generatePass returned nil",
				slog.String("session", sessionID),
				slog.Int("chunk", i+1))
			return fmt.Errorf("generatePass returned nil at chunk %d/%d", i+1, len(chunks))
		}

		if b, err := json.Marshal(newMap); err == nil {
			oldSummary = string(b)
		}

		sessionManager.SaveSummary(sessionID, newMap)
		if chunkLatest := latestMessageTime(chunk); chunkLatest > cursor {
			cursor = chunkLatest
		}
		sessionManager.SaveSummaryMeta(sessionID, cursor)
	}
	return nil
}

func chunkMessages(messages []agentTypes.Message, size int) [][]agentTypes.Message {
	if len(messages) == 0 {
		return nil
	}
	var chunks [][]agentTypes.Message
	for i := 0; i < len(messages); i += size {
		end := i + size
		if end > len(messages) {
			end = len(messages)
		}
		chunks = append(chunks, messages[i:end])
	}
	return chunks
}

func generatePass(ctx context.Context, agent agentTypes.Agent, oldSummary string, histories []agentTypes.Message) map[string]any {
	systemContent := strings.NewReplacer(
		"{{.Summary}}", oldSummary,
	).Replace(strings.TrimSpace(configs.SummaryPrompt))

	messages := []agentTypes.Message{
		{Role: "system", Content: systemContent},
	}
	for _, h := range histories {
		if h.Role != "user" && h.Role != "assistant" {
			continue
		}
		s, ok := h.Content.(string)
		if !ok {
			continue
		}
		messages = append(messages, agentTypes.Message{Role: h.Role, Content: s})
	}
	if n := len(messages); n > 0 && messages[n-1].Role == "assistant" {
		messages = append(messages, agentTypes.Message{
			Role:    "user",
			Content: "Generate the updated summary now per the rules above. Output raw JSON only.",
		})
	}

	return sendAndParse(ctx, agent, messages, "generatePass")
}

func sendAndParse(ctx context.Context, agent agentTypes.Agent, messages []agentTypes.Message, label string) map[string]any {
	resp, err := agent.Send(ctx, messages, nil)
	if err != nil {
		slog.Warn(label+" agent.Send",
			slog.String("error", err.Error()))
		return nil
	}
	if len(resp.Choices) == 0 {
		return nil
	}
	text, ok := resp.Choices[0].Message.Content.(string)
	if !ok {
		return nil
	}
	text = strings.TrimSpace(text)
	if text == "" {
		return nil
	}

	var result map[string]any
	for _, re := range []*regexp.Regexp{fencedBlockRegex, summaryTagRegex, summaryBracketRegex} {
		if loc := re.FindStringSubmatchIndex(text); loc != nil {
			part := text[loc[2]:loc[3]]
			if json.Unmarshal([]byte(part), &result) == nil && isSummaryJSON(result) {
				return result
			}
		}
	}

	dec := json.NewDecoder(bytes.NewReader([]byte(text)))
	for {
		tok, err := dec.Token()
		if err != nil {
			break
		}
		if delim, ok := tok.(json.Delim); ok && delim == '{' {
			start := strings.Index(text, "{")
			if start == -1 {
				break
			}
			var m map[string]any
			if json.Unmarshal([]byte(text[start:]), &m) == nil && isSummaryJSON(m) {
				return m
			}
			break
		}
	}
	return nil
}

func Get(histories []agentTypes.Message) []agentTypes.Message {
	var filtered []agentTypes.Message
	for _, h := range histories {
		if h.Role == "user" || h.Role == "assistant" {
			filtered = append(filtered, h)
		}
	}
	if len(filtered) == 0 {
		return nil
	}
	return filtered
}
