package memory

import (
	"context"
	"encoding/json"
	"sort"
	"sync"

	"github.com/google/uuid"

	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/port"
)

// Compile-time verification that PersonaRepository implements port.PersonaRepository.
var _ port.PersonaRepository = (*PersonaRepository)(nil)

// PersonaRepository is a thread-safe in-memory implementation of port.PersonaRepository.
type PersonaRepository struct {
	mu       sync.RWMutex
	personas map[domain.PersonaID]*domain.Persona
	handles  map[string]domain.PersonaID
}

// NewPersonaRepository creates a new in-memory persona repository.
func NewPersonaRepository() *PersonaRepository {
	return &PersonaRepository{
		personas: make(map[domain.PersonaID]*domain.Persona),
		handles:  make(map[string]domain.PersonaID),
	}
}

func (r *PersonaRepository) Create(_ context.Context, persona *domain.Persona) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if _, exists := r.handles[persona.Handle]; exists {
		return domain.ErrDuplicateHandle
	}

	if persona.ID.IsZero() {
		persona.ID = domain.PersonaID(uuid.New().String())
	}

	r.personas[persona.ID] = copyPersona(persona)
	r.handles[persona.Handle] = persona.ID
	return nil
}

func (r *PersonaRepository) GetByID(_ context.Context, id domain.PersonaID) (*domain.Persona, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	p, ok := r.personas[id]
	if !ok {
		return nil, domain.ErrPersonaNotFound
	}
	return copyPersona(p), nil
}

func (r *PersonaRepository) List(_ context.Context, limit, offset int) ([]*domain.Persona, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	all := make([]*domain.Persona, 0, len(r.personas))
	for _, p := range r.personas {
		all = append(all, p)
	}
	sort.Slice(all, func(i, j int) bool {
		return all[i].CreatedAt.After(all[j].CreatedAt)
	})

	if offset >= len(all) {
		return []*domain.Persona{}, nil
	}
	end := offset + limit
	if end > len(all) {
		end = len(all)
	}

	result := make([]*domain.Persona, 0, end-offset)
	for _, p := range all[offset:end] {
		result = append(result, copyPersona(p))
	}
	return result, nil
}

func (r *PersonaRepository) Update(_ context.Context, persona *domain.Persona) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	existing, ok := r.personas[persona.ID]
	if !ok {
		return domain.ErrPersonaNotFound
	}

	if existing.Handle != persona.Handle {
		if _, exists := r.handles[persona.Handle]; exists {
			return domain.ErrDuplicateHandle
		}
		delete(r.handles, existing.Handle)
		r.handles[persona.Handle] = persona.ID
	}

	r.personas[persona.ID] = copyPersona(persona)
	return nil
}

func copyPersona(p *domain.Persona) *domain.Persona {
	cp := *p
	if p.Tags != nil {
		cp.Tags = make([]string, len(p.Tags))
		copy(cp.Tags, p.Tags)
	}
	if p.ImageURLs != nil {
		cp.ImageURLs = make([]string, len(p.ImageURLs))
		copy(cp.ImageURLs, p.ImageURLs)
	}
	if p.VideoURLs != nil {
		cp.VideoURLs = make([]string, len(p.VideoURLs))
		copy(cp.VideoURLs, p.VideoURLs)
	}
	if p.SpecJSON != nil {
		cp.SpecJSON = make(json.RawMessage, len(p.SpecJSON))
		copy(cp.SpecJSON, p.SpecJSON)
	}
	return &cp
}
