package memory

import (
	"context"
	"sort"
	"strings"
	"sync"
	"time"
)

// Filter listeleme süzgecidir. UserID zorunludur: bir kullanıcı başkasının anısını asla görmez.
type Filter struct {
	UserID string
	Kind   Kind   // boş = hepsi
	Query  string // içerikte (büyük/küçük harf duyarsız) düz metin arama; boş = süzme yok
	Tag    string // boş = süzme yok
}

// Repository anı deposudur. Hem bellek içi hem MongoDB uygular; aynı sözleşmeden
// geçerler (bkz. RepositoryContract). Depo yalnızca saklar: doğrulama, embedding ve
// tekrar kararı Service'tedir.
type Repository interface {
	// Create anıyı ekler. Aynı kullanıcıda aynı Hash varsa ya da kimlik çakışırsa ErrDuplicate.
	Create(ctx context.Context, m *Memory) error
	// Get başkasının ya da olmayan anı için ErrNotFound döner.
	Get(ctx context.Context, userID, id string) (*Memory, error)
	// GetByHash kullanıcının aynı içerik özetli anısını döndürür; yoksa ErrNotFound.
	GetByHash(ctx context.Context, userID, hash string) (*Memory, error)
	// Update anıyı kimliğe göre TAMAMEN değiştirir (sahiplik denetimli). Yoksa ErrNotFound;
	// yeni Hash kullanıcının BAŞKA bir anısıyla çakışırsa ErrDuplicate.
	Update(ctx context.Context, m *Memory) error
	// SetEmbedding yalnızca vektörü yazar (kullanıcının eşzamanlı düzenlemesini ezmemek için).
	SetEmbedding(ctx context.Context, userID, id string, vec []float32, model string) error
	// Delete yoksa ErrNotFound.
	Delete(ctx context.Context, userID, id string) error
	// DeleteAll kullanıcının tüm anılarını siler ve sayısını döndürür.
	DeleteAll(ctx context.Context, userID string) (int, error)
	// List en son güncellenen önce olacak biçimde döner (eşitlikte ID). limit<=0: hepsi. Asla nil değil.
	List(ctx context.Context, f Filter, limit, offset int) ([]Memory, error)
	// Count kullanıcının anı sayısıdır.
	Count(ctx context.Context, userID string) (int, error)
	// Searchable kullanıcının tüm anılarını EMBEDDING'leriyle döndürür (Go tarafı skorlama için).
	Searchable(ctx context.Context, userID string) ([]Memory, error)
	// Touch anıların kullanım sayacını artırır ve LastUsedAt'i günceller. Olmayan kimlikler yok sayılır.
	Touch(ctx context.Context, userID string, ids []string, at time.Time) error
}

// MemoryRepo bellek içi depodur (süreç kapanınca veri gider). Eşzamanlı kullanıma güvenlidir.
type MemoryRepo struct {
	mu    sync.RWMutex
	items map[string]Memory
}

var _ Repository = (*MemoryRepo)(nil)

// NewMemoryRepo boş bir bellek içi depo üretir.
func NewMemoryRepo() *MemoryRepo { return &MemoryRepo{items: map[string]Memory{}} }

func (r *MemoryRepo) hashTaken(userID, hash, exceptID string) bool {
	for id, m := range r.items {
		if id != exceptID && m.UserID == userID && m.Hash == hash {
			return true
		}
	}
	return false
}

func (r *MemoryRepo) Create(_ context.Context, m *Memory) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, exists := r.items[m.ID]; exists || r.hashTaken(m.UserID, m.Hash, "") {
		return ErrDuplicate
	}
	r.items[m.ID] = m.Clone()
	return nil
}

func (r *MemoryRepo) Get(_ context.Context, userID, id string) (*Memory, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	m, ok := r.items[id]
	if !ok || m.UserID != userID {
		return nil, ErrNotFound
	}
	c := m.Clone()
	return &c, nil
}

func (r *MemoryRepo) GetByHash(_ context.Context, userID, hash string) (*Memory, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	for _, m := range r.items {
		if m.UserID == userID && m.Hash == hash {
			c := m.Clone()
			return &c, nil
		}
	}
	return nil, ErrNotFound
}

func (r *MemoryRepo) Update(_ context.Context, m *Memory) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	old, ok := r.items[m.ID]
	if !ok || old.UserID != m.UserID {
		return ErrNotFound
	}
	if r.hashTaken(m.UserID, m.Hash, m.ID) {
		return ErrDuplicate
	}
	r.items[m.ID] = m.Clone()
	return nil
}

func (r *MemoryRepo) SetEmbedding(_ context.Context, userID, id string, vec []float32, model string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	m, ok := r.items[id]
	if !ok || m.UserID != userID {
		return ErrNotFound
	}
	m.Embedding = append([]float32(nil), vec...)
	m.EmbedModel = model
	r.items[id] = m
	return nil
}

func (r *MemoryRepo) Delete(_ context.Context, userID, id string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	m, ok := r.items[id]
	if !ok || m.UserID != userID {
		return ErrNotFound
	}
	delete(r.items, id)
	return nil
}

func (r *MemoryRepo) DeleteAll(_ context.Context, userID string) (int, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	n := 0
	for id, m := range r.items {
		if m.UserID == userID {
			delete(r.items, id)
			n++
		}
	}
	return n, nil
}

func (r *MemoryRepo) List(_ context.Context, f Filter, limit, offset int) ([]Memory, error) {
	r.mu.RLock()
	all := make([]Memory, 0, len(r.items))
	q := strings.ToLower(strings.TrimSpace(f.Query))
	tag := strings.ToLower(strings.TrimSpace(f.Tag))
	for _, m := range r.items {
		if m.UserID != f.UserID {
			continue
		}
		if f.Kind != "" && m.Kind != f.Kind {
			continue
		}
		if q != "" && !strings.Contains(strings.ToLower(m.Content), q) {
			continue
		}
		if tag != "" && !containsString(m.Tags, tag) {
			continue
		}
		c := m.Clone()
		c.Embedding = nil // listede vektör taşınmaz
		all = append(all, c)
	}
	r.mu.RUnlock()

	sortNewestFirst(all)
	if offset < 0 {
		offset = 0
	}
	if offset >= len(all) {
		return []Memory{}, nil
	}
	all = all[offset:]
	if limit > 0 && len(all) > limit {
		all = all[:limit]
	}
	return all, nil
}

func (r *MemoryRepo) Count(_ context.Context, userID string) (int, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	n := 0
	for _, m := range r.items {
		if m.UserID == userID {
			n++
		}
	}
	return n, nil
}

func (r *MemoryRepo) Searchable(_ context.Context, userID string) ([]Memory, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]Memory, 0)
	for _, m := range r.items {
		if m.UserID == userID {
			out = append(out, m.Clone())
		}
	}
	sortNewestFirst(out)
	return out, nil
}

func (r *MemoryRepo) Touch(_ context.Context, userID string, ids []string, at time.Time) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	for _, id := range ids {
		m, ok := r.items[id]
		if !ok || m.UserID != userID {
			continue
		}
		m.UseCount++
		t := at
		m.LastUsedAt = &t
		r.items[id] = m
	}
	return nil
}

// sortNewestFirst en son güncellenen önce; eşitlikte kimliğe göre (belirli sıra).
func sortNewestFirst(ms []Memory) {
	sort.SliceStable(ms, func(i, j int) bool {
		if !ms[i].UpdatedAt.Equal(ms[j].UpdatedAt) {
			return ms[i].UpdatedAt.After(ms[j].UpdatedAt)
		}
		return ms[i].ID < ms[j].ID
	})
}

func containsString(list []string, s string) bool {
	for _, x := range list {
		if x == s {
			return true
		}
	}
	return false
}
