package persona

import (
	"context"
	"errors"
	"sort"
	"sync"
)

// MemoryRepository, profilleri bellekte tutan uygulamadır.
//
// Ne zaman? (1) Birim testler hızlı ve Mongo'suz çalışsın diye (memory/notify
// paketlerinin deseni). (2) MONGO kapalıysa sistem yine ayakta kalsın diye.
// Kalıcılık gerektiği için üretimde Mongo uygulaması seçilir (main.go).
type MemoryRepository struct {
	mu   sync.RWMutex
	data map[string]Profile
	// byUser ters indeks: her turda tüm profilleri taramak yerine kullanıcının
	// profillerini doğrudan bulur (tüm liste zaten bellekte olduğu için bu
	// index biraz hız kazandırır; Mongo'da da aynı index mantığı sorguya yansır).
	byUser map[string]map[string]bool
}

var _ Repository = (*MemoryRepository)(nil)

// NewMemoryRepository boş bellek deposu üretir.
func NewMemoryRepository() *MemoryRepository {
	return &MemoryRepository{
		data:   make(map[string]Profile),
		byUser: make(map[string]map[string]bool),
	}
}

// List tüm profilleri ada göre sıralı döner (arayüzde kararlı sıra).
func (r *MemoryRepository) List(_ context.Context) ([]Profile, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	out := make([]Profile, 0, len(r.data))
	for _, p := range r.data {
		out = append(out, p.Clone())
	}
	sort.Slice(out, func(i, j int) bool {
		if out[i].Name == out[j].Name {
			return out[i].ID < out[j].ID
		}
		return out[i].Name < out[j].Name
	})
	return out, nil
}

// GetByUser, kullanıcıya atanmış profilleri döner.
func (r *MemoryRepository) GetByUser(_ context.Context, userID string) ([]Profile, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	ids := r.byUser[userID]
	out := make([]Profile, 0, len(ids))
	for id := range ids {
		if p, ok := r.data[id]; ok {
			out = append(out, p.Clone())
		}
	}
	sort.Slice(out, func(i, j int) bool {
		if out[i].Name == out[j].Name {
			return out[i].ID < out[j].ID
		}
		return out[i].Name < out[j].Name
	})
	return out, nil
}

// Get tek profili döner (yoksa ErrNotFound).
func (r *MemoryRepository) Get(_ context.Context, id string) (Profile, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	p, ok := r.data[id]
	if !ok {
		return Profile{}, ErrNotFound
	}
	return p.Clone(), nil
}

// Create profili kaydeder.
func (r *MemoryRepository) Create(_ context.Context, p Profile) (Profile, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, exists := r.data[p.ID]; exists {
		return Profile{}, errors.New("persona: kimlik zaten var")
	}
	r.put(p)
	return p.Clone(), nil
}

// Update mevcut profili değiştirir (yoksa ErrNotFound).
func (r *MemoryRepository) Update(_ context.Context, p Profile) (Profile, error) {
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, ok := r.data[p.ID]; !ok {
		return Profile{}, ErrNotFound
	}
	r.put(p) // eski atamaları temizler, yenileri yazar
	return p.Clone(), nil
}

// Delete profili siler (yoksa ErrNotFound).
func (r *MemoryRepository) Delete(_ context.Context, id string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	p, ok := r.data[id]
	if !ok {
		return ErrNotFound
	}
	delete(r.data, id)
	for _, u := range p.AssignedUserIDs {
		if set, ok := r.byUser[u]; ok {
			delete(set, id)
			if len(set) == 0 {
				delete(r.byUser, u)
			}
		}
	}
	return nil
}

// put, kaydı ve ters indeksi yazar (kilit ALINMIŞ olmalıdır).
func (r *MemoryRepository) put(p Profile) {
	// Eski atamaları temizle: güncellemede kullanıcı listesi değişebilir.
	if old, ok := r.data[p.ID]; ok {
		for _, u := range old.AssignedUserIDs {
			if set, ok := r.byUser[u]; ok {
				delete(set, p.ID)
				if len(set) == 0 {
					delete(r.byUser, u)
				}
			}
		}
	}
	stored := p.Clone()
	r.data[p.ID] = stored
	for _, u := range stored.AssignedUserIDs {
		if r.byUser[u] == nil {
			r.byUser[u] = make(map[string]bool)
		}
		r.byUser[u][p.ID] = true
	}
}
