package conversation

import (
	"context"
	"errors"
	"strings"
	"time"
	"unicode/utf8"

	"fiko-ai/pkg/id"
)

// ErrEmptyContent boş (yalnızca boşluk) kullanıcı mesajı için döner.
var ErrEmptyContent = errors.New("conversation: mesaj boş")

const maxTitleRunes = 48

// Service konuşma iş kurallarını yürütür: sahiplik, otomatik başlık, mesaj kaydı.
type Service struct {
	repo Repository
	now  func() time.Time
}

func NewService(repo Repository) *Service {
	return &Service{repo: repo, now: func() time.Time { return time.Now().UTC() }}
}

// AssistantMessage asistan cevabını kaydetmek için gereken alanlardır.
// ID verilmezse üretilir; ajan, "thinking" olayında istemciye verdiği kimliği burada geri iletir.
type AssistantMessage struct {
	ID               string
	Content          string
	Status           MessageStatus
	Model            string
	PromptTokens     int
	CompletionTokens int
	LatencyMs        int64
	// ToolRuns cevabı üretirken çalışan tool'ların kaydıdır (Phase 5 execution tracking).
	ToolRuns []ToolRun
	// ToolConfirms cevabın doğurduğu onay/bilgi formu kartlarıdır (kalıcı; yenilemede geri gelir).
	ToolConfirms []ToolConfirm
}

func (s *Service) Create(ctx context.Context, userID string) (*Conversation, error) {
	now := s.now()
	c := &Conversation{
		ID:        id.New("conv"),
		UserID:    userID,
		SessionID: id.New("sess"),
		Title:     DefaultTitle,
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := s.repo.CreateConversation(ctx, c); err != nil {
		return nil, err
	}
	return c, nil
}

// Get konuşmayı döndürür. Başka kullanıcıya ait konuşma, varlığı sızdırılmamak için ErrNotFound sayılır.
func (s *Service) Get(ctx context.Context, userID, convID string) (*Conversation, error) {
	c, err := s.repo.GetConversation(ctx, convID)
	if err != nil {
		return nil, err
	}
	if c.UserID != userID {
		return nil, ErrNotFound
	}
	return c, nil
}

func (s *Service) List(ctx context.Context, userID string, limit, offset int) ([]Conversation, error) {
	return s.repo.ListConversations(ctx, userID, limit, offset)
}

func (s *Service) Delete(ctx context.Context, userID, convID string) error {
	if _, err := s.Get(ctx, userID, convID); err != nil {
		return err
	}
	return s.repo.DeleteConversation(ctx, convID)
}

// Messages konuşmanın geçmişini (son limit mesaj, eskiden yeniye) döndürür; sahiplik denetlenir.
func (s *Service) Messages(ctx context.Context, userID, convID string, limit int) ([]Message, error) {
	if _, err := s.Get(ctx, userID, convID); err != nil {
		return nil, err
	}
	return s.repo.Messages(ctx, convID, limit)
}

// AddUserMessage kullanıcı mesajını kaydeder. Konuşma hâlâ varsayılan başlıktaysa başlığı
// mesajdan türetir ve yeni başlığı döndürür (değişmediyse boş string).
func (s *Service) AddUserMessage(ctx context.Context, userID, convID, text string) (*Message, string, error) {
	text = strings.TrimSpace(text)
	if text == "" {
		return nil, "", ErrEmptyContent
	}
	c, err := s.Get(ctx, userID, convID)
	if err != nil {
		return nil, "", err
	}
	m := &Message{
		ID:             id.New("msg"),
		ConversationID: convID,
		Role:           RoleUser,
		Content:        text,
		Status:         StatusComplete,
		CreatedAt:      s.now(),
	}
	if err := s.repo.AppendMessage(ctx, m); err != nil {
		return nil, "", err
	}
	var newTitle string
	if c.Title == DefaultTitle {
		if t := TitleFrom(text); t != "" {
			if err := s.repo.UpdateTitle(ctx, convID, t); err != nil {
				return m, "", err
			}
			newTitle = t
		}
	}
	return m, newTitle, nil
}

// AddAssistantMessage asistan cevabını (tamamlanmış, yarıda kesilmiş ya da hatalı) kaydeder.
func (s *Service) AddAssistantMessage(ctx context.Context, convID string, a AssistantMessage) (*Message, error) {
	if a.ID == "" {
		a.ID = id.New("msg")
	}
	if a.Status == "" {
		a.Status = StatusComplete
	}
	m := &Message{
		ID:               a.ID,
		ConversationID:   convID,
		Role:             RoleAssistant,
		Content:          a.Content,
		Status:           a.Status,
		Model:            a.Model,
		PromptTokens:     a.PromptTokens,
		CompletionTokens: a.CompletionTokens,
		LatencyMs:        a.LatencyMs,
		ToolRuns:         a.ToolRuns,
		ToolConfirms:     a.ToolConfirms,
		CreatedAt:        s.now(),
	}
	if err := s.repo.AppendMessage(ctx, m); err != nil {
		return nil, err
	}
	return m, nil
}

// ResolveToolConfirm bekleyen bir onay kartını kapatır (answered | cancelled | expired).
//
// Yalnızca hâlâ "pending" olan kart değişir: aynı kartın ikinci kez cevaplanması (çift tıklama,
// ikinci sekme) ilk sonucu ezmez. Kart bulunamazsa ya da zaten kapanmışsa ErrNotFound döner;
// çağıran bunu hata saymaz. Konuşma başka kullanıcıya aitse de ErrNotFound döner.
func (s *Service) ResolveToolConfirm(ctx context.Context, userID, convID, confirmID string, r ToolConfirmResolution) error {
	if _, err := s.Get(ctx, userID, convID); err != nil {
		return err
	}
	return s.repo.ResolveToolConfirm(ctx, convID, confirmID, r)
}

// SetTitle konuşmanın başlığını değiştirir. Başlık boşsa değişiklik yapılmaz ve false döner.
// Konuşma yoksa hata döner.
func (s *Service) SetTitle(ctx context.Context, convID, title string) (bool, error) {
	title = strings.TrimSpace(title)
	if title == "" {
		return false, nil
	}
	if _, err := s.repo.GetConversation(ctx, convID); err != nil {
		return false, err
	}
	if err := s.repo.UpdateTitle(ctx, convID, title); err != nil {
		return false, err
	}
	return true, nil
}

// TitleFrom metnin ilk dolu satırından, boşlukları sadeleştirip en çok 48 karakterlik başlık üretir.
func TitleFrom(text string) string {
	for _, line := range strings.Split(text, "\n") {
		line = strings.Join(strings.Fields(line), " ")
		if line == "" {
			continue
		}
		if utf8.RuneCountInString(line) > maxTitleRunes {
			r := []rune(line)
			line = strings.TrimSpace(string(r[:maxTitleRunes])) + "…"
		}
		return line
	}
	return ""
}
