package memory

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

	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/domain"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/port"
)

// Compile-time interface check.
var _ port.MediaRepository = (*MediaRepository)(nil)

// MediaRepository is an in-memory media metadata store for standalone development.
type MediaRepository struct {
	mu      sync.RWMutex
	objects map[domain.MediaObjectID]*domain.MediaObject
	byPath  map[string]domain.MediaObjectID
}

// NewMediaRepository creates a new in-memory media repository.
func NewMediaRepository() *MediaRepository {
	return &MediaRepository{
		objects: make(map[domain.MediaObjectID]*domain.MediaObject),
		byPath:  make(map[string]domain.MediaObjectID),
	}
}

func (r *MediaRepository) copyObject(obj *domain.MediaObject) *domain.MediaObject {
	cp := *obj
	return &cp
}

func (r *MediaRepository) Create(_ context.Context, obj *domain.MediaObject) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.objects[obj.ID] = r.copyObject(obj)
	r.byPath[obj.Path] = obj.ID
	return nil
}

func (r *MediaRepository) Get(_ context.Context, id domain.MediaObjectID) (*domain.MediaObject, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	obj, ok := r.objects[id]
	if !ok || obj.DeletedAt != nil {
		return nil, domain.ErrNotFound
	}
	return r.copyObject(obj), nil
}

func (r *MediaRepository) ListByUser(_ context.Context, userID domain.UserID, opts port.ListMediaOptions) ([]domain.MediaObject, int, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	var all []domain.MediaObject
	for _, obj := range r.objects {
		if obj.UserID != userID || obj.DeletedAt != nil {
			continue
		}
		if opts.ContentTypePrefix != "" && !strings.HasPrefix(obj.ContentType, opts.ContentTypePrefix) {
			continue
		}
		all = append(all, *r.copyObject(obj))
	}

	// Sort by created_at DESC
	sort.Slice(all, func(i, j int) bool {
		return all[i].CreatedAt.After(all[j].CreatedAt)
	})

	total := len(all)

	// Apply pagination
	limit := opts.Limit
	if limit <= 0 {
		limit = 50
	}
	offset := opts.Offset
	if offset > len(all) {
		offset = len(all)
	}
	end := offset + limit
	if end > len(all) {
		end = len(all)
	}

	return all[offset:end], total, nil
}

func (r *MediaRepository) SoftDelete(_ context.Context, id domain.MediaObjectID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	obj, ok := r.objects[id]
	if !ok {
		return domain.ErrNotFound
	}
	now := time.Now()
	obj.DeletedAt = &now
	return nil
}

func (r *MediaRepository) HardDelete(_ context.Context, id domain.MediaObjectID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	obj, ok := r.objects[id]
	if !ok {
		return domain.ErrNotFound
	}
	delete(r.byPath, obj.Path)
	delete(r.objects, id)
	return nil
}

func (r *MediaRepository) GetByPath(_ context.Context, path string) (*domain.MediaObject, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	id, ok := r.byPath[path]
	if !ok {
		return nil, domain.ErrNotFound
	}
	obj, ok := r.objects[id]
	if !ok || obj.DeletedAt != nil {
		return nil, domain.ErrNotFound
	}
	return r.copyObject(obj), nil
}
