// Package mongostore conversation.Repository arayüzünün MongoDB uygulamasıdır.
//
// Bu dosya SADECE depolama yapar; iş kuralları (sahiplik, otomatik başlık) conversation.Service'tedir.
// Sözleşmenin tek doğrulama yolu conversation.RepositoryContract'tır: bu depo aynı testten geçer,
// bellek içi depo ile birebir aynı davranışı taşır.
package mongostore

import (
	"context"
	"errors"
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"fiko-ai/internal/conversation"
)

// Repo MongoDB üzerindeki konuşma deposudur.
type Repo struct {
	convs    *mongo.Collection
	messages *mongo.Collection
}

var _ conversation.Repository = (*Repo)(nil)

// New depo oluşturur. db *mongo.Database verilmelidir.
func New(db *mongo.Database) *Repo {
	return &Repo{
		convs:    db.Collection("conversations"),
		messages: db.Collection("messages"),
	}
}

// CreateConversation konuşmayı ekler. Aynı kimlik varsa hata döner (duplicate key).
func (r *Repo) CreateConversation(ctx context.Context, c *conversation.Conversation) error {
	if c == nil || c.ID == "" {
		return errors.New("mongostore: konuşma kimliği gerekli")
	}
	doc := *c
	doc.Messages = nil // mesajlar ayrı koleksiyonda
	if _, err := r.convs.InsertOne(ctx, doc); err != nil {
		return fmt.Errorf("mongostore: konuşma eklenemedi: %w", err)
	}
	return nil
}

// GetConversation konuşmayı döndürür; yoksa conversation.ErrNotFound.
func (r *Repo) GetConversation(ctx context.Context, id string) (*conversation.Conversation, error) {
	var c conversation.Conversation
	err := r.convs.FindOne(ctx, bson.M{"_id": id}).Decode(&c)
	if errors.Is(err, mongo.ErrNoDocuments) {
		return nil, conversation.ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("mongostore: konuşma okunamadı: %w", err)
	}
	c.Messages = nil
	return &c, nil
}

// ListConversations kullanıcının konuşmalarını UpdatedAt'e göre yeniden eskiye listeler.
// limit<=0 tümünü, offset<0 sıfır kabul edilir.
func (r *Repo) ListConversations(ctx context.Context, userID string, limit, offset int) ([]conversation.Conversation, error) {
	if offset < 0 {
		offset = 0
	}
	opts := options.Find().SetSort(bson.D{{Key: "updatedAt", Value: -1}})
	if limit > 0 {
		opts.SetLimit(int64(limit)).SetSkip(int64(offset))
	} else if offset > 0 {
		opts.SetSkip(int64(offset))
	}

	cur, err := r.convs.Find(ctx, bson.M{"userId": userID}, opts)
	if err != nil {
		return nil, fmt.Errorf("mongostore: konuşmalar listelenemedi: %w", err)
	}
	defer cur.Close(ctx)

	out := make([]conversation.Conversation, 0)
	if err := cur.All(ctx, &out); err != nil {
		return nil, fmt.Errorf("mongostore: sonuçlar okunamadı: %w", err)
	}
	for i := range out {
		out[i].Messages = nil
	}
	return out, nil
}

// UpdateTitle başlığı günceller; konuşma yoksa conversation.ErrNotFound.
func (r *Repo) UpdateTitle(ctx context.Context, id, title string) error {
	res, err := r.convs.UpdateOne(ctx,
		bson.M{"_id": id},
		bson.M{"$set": bson.M{"title": title, "updatedAt": time.Now().UTC()}},
	)
	if err != nil {
		return fmt.Errorf("mongostore: başlık güncellenemedi: %w", err)
	}
	if res.MatchedCount == 0 {
		return conversation.ErrNotFound
	}
	return nil
}

// DeleteConversation konuşmayı ve tüm mesajlarını siler; yoksa conversation.ErrNotFound.
func (r *Repo) DeleteConversation(ctx context.Context, id string) error {
	res, err := r.convs.DeleteOne(ctx, bson.M{"_id": id})
	if err != nil {
		return fmt.Errorf("mongostore: konuşma silinemedi: %w", err)
	}
	if res.DeletedCount == 0 {
		return conversation.ErrNotFound
	}
	// Mesajlar silinmezse alan boşa çıkar ve geçmiş gizlice kalır; temizlik şart.
	if _, err := r.messages.DeleteMany(ctx, bson.M{"conversationId": id}); err != nil {
		return fmt.Errorf("mongostore: mesajlar silinemedi: %w", err)
	}
	return nil
}

// AppendMessage mesajı ekler ve konuşmanın UpdatedAt'ini ilerletir (yoksa ErrNotFound).
// Önce konuşmanın varlığı denetlenir: konuşma olmadan mesaj anlamsız.
// Messages konuşmanın mesajlarını CreatedAt'e göre eskiden yeniye döndürür.
// limit>0 ise YALNIZCA SON limit mesaj döner (bellek içi depo ile aynı sözleşme).
func (r *Repo) Messages(ctx context.Context, conversationID string, limit int) ([]conversation.Message, error) {
	// Konuşma yoksa mesaj da yoktur; sözleşme ErrNotFound bekler.
	total, err := r.messages.CountDocuments(ctx, bson.M{"conversationId": conversationID})
	if err != nil {
		return nil, fmt.Errorf("mongostore: mesaj sayılamadı: %w", err)
	}
	if total == 0 {
		// Mesaj yok ama konuşma var olabilir → ayırt et.
		if n, err := r.convs.CountDocuments(ctx, bson.M{"_id": conversationID}); err != nil {
			return nil, fmt.Errorf("mongostore: konuşma sayılamadı: %w", err)
		} else if n == 0 {
			return nil, conversation.ErrNotFound
		}
	}

	// Son N mesaj: toplamdan fazlasını atla, kalanı eskiden yeniye sırala.
	var skip int64
	if limit > 0 && total > int64(limit) {
		skip = total - int64(limit)
	}
	opts := options.Find().SetSort(bson.D{{Key: "createdAt", Value: 1}})
	if skip > 0 {
		opts.SetSkip(skip)
	}

	cur, err := r.messages.Find(ctx, bson.M{"conversationId": conversationID}, opts)
	if err != nil {
		return nil, fmt.Errorf("mongostore: mesajlar okunamadı: %w", err)
	}
	defer cur.Close(ctx)

	out := make([]conversation.Message, 0)
	if err := cur.All(ctx, &out); err != nil {
		return nil, fmt.Errorf("mongostore: sonuçlar okunamadı: %w", err)
	}
	return out, nil
}
func (r *Repo) AppendMessage(ctx context.Context, m *conversation.Message) error {
	if m == nil || m.ConversationID == "" {
		return errors.New("mongostore: konuşma kimliği gerekli")
	}
	res, err := r.convs.UpdateOne(ctx,
		bson.M{"_id": m.ConversationID},
		bson.M{"$set": bson.M{"updatedAt": time.Now().UTC()}},
	)
	if err != nil {
		return fmt.Errorf("mongostore: konuşma güncellenemedi: %w", err)
	}
	if res.MatchedCount == 0 {
		return conversation.ErrNotFound
	}
	if _, err := r.messages.InsertOne(ctx, m); err != nil {
		return fmt.Errorf("mongostore: mesaj eklenemedi: %w", err)
	}
	return nil
}

// ResolveToolConfirm konuşmadaki, hâlâ "pending" olan onay kartını kapatır.
// $elemMatch yalnızca bekleyen kartı eşler; konumsal $ aynı elemanı günceller. Kart yoksa
// ya da zaten kapanmışsa conversation.ErrNotFound döner.
func (r *Repo) ResolveToolConfirm(ctx context.Context, conversationID, confirmID string, res conversation.ToolConfirmResolution) error {
	set := bson.M{
		"toolConfirms.$.state":      res.State,
		"toolConfirms.$.summary":    res.Summary,
		"toolConfirms.$.resolvedAt": time.Now().UTC(),
	}
	if res.Values != nil {
		set["toolConfirms.$.values"] = res.Values
	}
	out, err := r.messages.UpdateOne(ctx,
		bson.M{
			"conversationId": conversationID,
			"toolConfirms": bson.M{"$elemMatch": bson.M{
				"confirmId": confirmID, "state": conversation.ConfirmPending,
			}},
		},
		bson.M{"$set": set},
	)
	if err != nil {
		return fmt.Errorf("mongostore: onay kartı güncellenemedi: %w", err)
	}
	if out.MatchedCount == 0 {
		return conversation.ErrNotFound
	}
	return nil
}
