package api

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/muyue/muyue/internal/config"
)

const contextWindowTokens = 150000
const summarizeRatio = 0.80
const charsPerToken = 4

func extractDisplayContent(role, content string) string {
	if role != "assistant" {
		return content
	}
	var parsed struct {
		Content     string `json:"content"`
		ToolCalls   []struct {
			Name string `json:"name"`
			Args string `json:"args"`
		} `json:"tool_calls"`
		ToolResults []struct {
			Name   string `json:"name"`
			Result string `json:"result"`
		} `json:"tool_results"`
	}
	if err := json.Unmarshal([]byte(content), &parsed); err != nil {
		return content
	}
	var sb strings.Builder
	if parsed.Content != "" {
		sb.WriteString(parsed.Content)
	}
	for _, tc := range parsed.ToolCalls {
		sb.WriteString("\n[")
		sb.WriteString(tc.Name)
		sb.WriteString("] ")
		sb.WriteString(tc.Args)
	}
	for _, tr := range parsed.ToolResults {
		sb.WriteString("\n[result")
		if tr.Name != "" {
			sb.WriteString(":")
			sb.WriteString(tr.Name)
		}
		sb.WriteString("] ")
		sb.WriteString(tr.Result)
	}
	return sb.String()
}

type FeedMessage struct {
	ID         string   `json:"id"`
	Role       string   `json:"role"`
	Content    string   `json:"content"`
	Time       string   `json:"time"`
	Images     []string `json:"images,omitempty"`
	Summarized bool     `json:"summarized,omitempty"`
}

type Conversation struct {
	Messages  []FeedMessage `json:"messages"`
	Summary   string        `json:"summary,omitempty"`
	CreatedAt string        `json:"created_at"`
	UpdatedAt string        `json:"updated_at"`
}

type ConversationStore struct {
	mu   sync.RWMutex
	path string
	conv *Conversation
}

type TokenCount struct {
	total     int
	byRole    map[string]int
	byMessage int
}

type SearchResult struct {
	ID      string `json:"id"`
	Role    string `json:"role"`
	Content string `json:"content"`
	Time    string `json:"time"`
}

func NewConversationStore() *ConversationStore {
	dir, err := config.ConfigDir()
	if err != nil {
		dir = "/tmp/muyue"
	}
	path := filepath.Join(dir, "conversation.json")
	cs := &ConversationStore{path: path}
	cs.load()
	return cs
}

func (cs *ConversationStore) load() {
	data, err := os.ReadFile(cs.path)
	if err != nil {
		cs.conv = &Conversation{
			Messages:  []FeedMessage{},
			CreatedAt: time.Now().Format(time.RFC3339),
			UpdatedAt: time.Now().Format(time.RFC3339),
		}
		return
	}
	var conv Conversation
	if err := json.Unmarshal(data, &conv); err != nil {
		cs.conv = &Conversation{
			Messages:  []FeedMessage{},
			CreatedAt: time.Now().Format(time.RFC3339),
			UpdatedAt: time.Now().Format(time.RFC3339),
		}
		return
	}
	if conv.Messages == nil {
		conv.Messages = []FeedMessage{}
	}
	cs.conv = &conv
}

func (cs *ConversationStore) save() error {
	cs.conv.UpdatedAt = time.Now().Format(time.RFC3339)
	data, err := json.MarshalIndent(cs.conv, "", "  ")
	if err != nil {
		return err
	}
	dir := filepath.Dir(cs.path)
	os.MkdirAll(dir, 0755)
	return os.WriteFile(cs.path, data, 0600)
}

func (cs *ConversationStore) Get() []FeedMessage {
	cs.mu.RLock()
	defer cs.mu.RUnlock()
	out := make([]FeedMessage, len(cs.conv.Messages))
	copy(out, cs.conv.Messages)
	return out
}

func (cs *ConversationStore) GetSummary() string {
	cs.mu.RLock()
	defer cs.mu.RUnlock()
	return cs.conv.Summary
}

func (cs *ConversationStore) Add(role, content string) FeedMessage {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	msg := FeedMessage{
		ID:      generateMsgID(),
		Role:    role,
		Content: content,
		Time:    time.Now().Format(time.RFC3339),
	}
	cs.conv.Messages = append(cs.conv.Messages, msg)
	cs.save()
	return msg
}

func (cs *ConversationStore) AddWithImages(role, content string, imageIDs []string) FeedMessage {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	msg := FeedMessage{
		ID:      generateMsgID(),
		Role:    role,
		Content: content,
		Time:    time.Now().Format(time.RFC3339),
		Images:  imageIDs,
	}
	cs.conv.Messages = append(cs.conv.Messages, msg)
	cs.save()
	return msg
}

func (cs *ConversationStore) Clear() {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	var imageIDs []string
	for _, m := range cs.conv.Messages {
		imageIDs = append(imageIDs, m.Images...)
	}

	cs.conv.Messages = []FeedMessage{}
	cs.conv.Summary = ""
	cs.conv.CreatedAt = time.Now().Format(time.RFC3339)
	cs.conv.UpdatedAt = time.Now().Format(time.RFC3339)
	cs.save()

	go cleanupImages(imageIDs)
}

func (cs *ConversationStore) SetSummary(summary string) {
	cs.mu.Lock()
	defer cs.mu.Unlock()
	cs.conv.Summary = summary
	cs.save()
}

func (cs *ConversationStore) MarkSummarized(upToIndex int) {
	cs.mu.Lock()
	defer cs.mu.Unlock()
	if upToIndex <= 0 || upToIndex >= len(cs.conv.Messages) {
		return
	}
	for i := 0; i < upToIndex; i++ {
		cs.conv.Messages[i].Summarized = true
	}
	cs.save()
}

func (cs *ConversationStore) ApproxTokenCount() int {
	return cs.ApproxTokenCountDetailed().total
}

func (cs *ConversationStore) ApproxTokenCountDetailed() TokenCount {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	result := TokenCount{
		byRole: make(map[string]int),
	}

	for _, m := range cs.conv.Messages {
		if m.Role == "system" || m.Summarized {
			continue
		}
		count := utf8.RuneCountInString(extractDisplayContent(m.Role, m.Content)) / charsPerToken
		result.byMessage += count
		result.byRole[m.Role] += count
	}

	if cs.conv.Summary != "" {
		result.total = result.byMessage + utf8.RuneCountInString(cs.conv.Summary)/charsPerToken
	} else {
		result.total = result.byMessage
	}

	return result
}

func (cs *ConversationStore) NeedsSummarization() bool {
	return cs.ApproxTokenCount() > int(float64(contextWindowTokens)*summarizeRatio)
}

func (cs *ConversationStore) Search(query string) []SearchResult {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	var results []SearchResult
	queryLower := strings.ToLower(query)

	for _, msg := range cs.conv.Messages {
		if strings.Contains(strings.ToLower(msg.Content), queryLower) {
			results = append(results, SearchResult{
				ID:      msg.ID,
				Role:    msg.Role,
				Content: msg.Content,
				Time:    msg.Time,
			})
		}
	}

	return results
}

func (cs *ConversationStore) ExportMarkdown() string {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	var sb strings.Builder
	sb.WriteString("# Conversation Export\n\n")
	sb.WriteString(fmt.Sprintf("Exporté le: %s\n\n", time.Now().Format(time.RFC3339)))

	if cs.conv.Summary != "" {
		sb.WriteString("## Résumé\n\n")
		sb.WriteString(cs.conv.Summary)
		sb.WriteString("\n\n---\n\n")
	}

	sb.WriteString("## Messages\n\n")

	for i, msg := range cs.conv.Messages {
		roleLabel := msg.Role
		if roleLabel == "user" {
			roleLabel = "👤 Utilisateur"
		} else if roleLabel == "assistant" {
			roleLabel = "🤖 Assistant"
		} else if roleLabel == "system" {
			roleLabel = "⚙️ Système"
		}

		timestamp := ""
		if msg.Time != "" {
			if t, err := time.Parse(time.RFC3339, msg.Time); err == nil {
				timestamp = t.Format("2006-01-02 15:04")
			}
		}

		sb.WriteString(fmt.Sprintf("### [%d] %s (%s)\n\n", i+1, roleLabel, timestamp))
		sb.WriteString(msg.Content)
		sb.WriteString("\n\n---\n\n")
	}

	return sb.String()
}

func (cs *ConversationStore) ExportJSON() string {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	data, err := json.MarshalIndent(cs.conv, "", "  ")
	if err != nil {
		return "{}"
	}
	return string(data)
}

func generateMsgID() string {
	return time.Now().Format("20060102150405.000") + "-" + fmt.Sprintf("%d", time.Now().UnixNano())
}