package memory

import (
	"context"
	"fmt"
	"sync"
	"time"

	"git.threesix.ai/jordan/persona-community-5/pkg/album"
)

// AlbumRepository is an in-memory implementation of port.AlbumRepository.
// Used in standalone dev mode (no DATABASE_URL). Not safe for persistence across restarts.
type AlbumRepository struct {
	mu     sync.RWMutex
	albums map[album.AlbumID]*album.Album
}

// NewAlbumRepository creates an in-memory album repository.
func NewAlbumRepository() *AlbumRepository {
	return &AlbumRepository{
		albums: make(map[album.AlbumID]*album.Album),
	}
}

// Create persists a new album. The caller must set ID, Name, SubjectDesc, Shots before calling.
func (r *AlbumRepository) Create(ctx context.Context, a *album.Album) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	now := time.Now().UTC()
	a.CreatedAt = now
	a.UpdatedAt = now
	copy := *a
	r.albums[a.ID] = &copy
	return nil
}

// Get returns an album by ID and userID. Returns error if not found or wrong user.
func (r *AlbumRepository) Get(ctx context.Context, id album.AlbumID, userID string) (*album.Album, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return nil, fmt.Errorf("album not found: %s", id)
	}
	copy := *a
	shots := make([]album.Shot, len(a.Shots))
	copy.Shots = shots
	for i, s := range a.Shots {
		shots[i] = s
	}
	return &copy, nil
}

// List returns all albums for a user, ordered by CreatedAt DESC.
func (r *AlbumRepository) List(ctx context.Context, userID string) ([]album.Album, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	var result []album.Album
	for _, a := range r.albums {
		if a.UserID != userID {
			continue
		}
		copy := *a
		shots := make([]album.Shot, len(a.Shots))
		for i, s := range a.Shots {
			shots[i] = s
		}
		copy.Shots = shots
		result = append(result, copy)
	}
	// Sort by CreatedAt DESC (simple insertion sort — in-memory is small).
	for i := 1; i < len(result); i++ {
		for j := i; j > 0 && result[j].CreatedAt.After(result[j-1].CreatedAt); j-- {
			result[j], result[j-1] = result[j-1], result[j]
		}
	}
	return result, nil
}

// Delete removes an album by ID and userID.
func (r *AlbumRepository) Delete(ctx context.Context, id album.AlbumID, userID string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return fmt.Errorf("album not found: %s", id)
	}
	delete(r.albums, id)
	return nil
}

// UpdateAnchor stores the generated anchor URL.
func (r *AlbumRepository) UpdateAnchor(ctx context.Context, id album.AlbumID, userID, anchorURL, anchorJobID string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return fmt.Errorf("album not found: %s", id)
	}
	a.AnchorURL = anchorURL
	a.AnchorJobID = anchorJobID
	a.UpdatedAt = time.Now().UTC()
	return nil
}

// UpdateShot stores the generated image URL and status for a specific shot.
func (r *AlbumRepository) UpdateShot(ctx context.Context, id album.AlbumID, userID string, shotIndex int, imageURL string, status album.ShotStatus, shotError string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return fmt.Errorf("album not found: %s", id)
	}
	if shotIndex < 0 || shotIndex >= len(a.Shots) {
		return fmt.Errorf("shot index out of range: %d", shotIndex)
	}
	now := time.Now().UTC()
	a.Shots[shotIndex].ImageURL = imageURL
	a.Shots[shotIndex].Status = status
	a.Shots[shotIndex].Error = shotError
	if status == album.ShotComplete {
		a.Shots[shotIndex].GeneratedAt = &now
	}
	a.UpdatedAt = now
	return nil
}

// ResetShot clears a shot back to pending.
func (r *AlbumRepository) ResetShot(ctx context.Context, id album.AlbumID, userID string, shotIndex int) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return fmt.Errorf("album not found: %s", id)
	}
	if shotIndex < 0 || shotIndex >= len(a.Shots) {
		return fmt.Errorf("shot index out of range: %d", shotIndex)
	}
	a.Shots[shotIndex].ImageURL = ""
	a.Shots[shotIndex].JobID = ""
	a.Shots[shotIndex].Status = album.ShotPending
	a.Shots[shotIndex].Error = ""
	a.Shots[shotIndex].GeneratedAt = nil
	a.UpdatedAt = time.Now().UTC()
	return nil
}

// UpdateAnchorJobID stores the anchor job ID when the anchor generation is enqueued.
func (r *AlbumRepository) UpdateAnchorJobID(ctx context.Context, id album.AlbumID, userID, jobID string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return fmt.Errorf("album not found: %s", id)
	}
	a.AnchorJobID = jobID
	a.UpdatedAt = time.Now().UTC()
	return nil
}

// UpdateShotJobID stores the job ID for a shot when its generation is enqueued.
func (r *AlbumRepository) UpdateShotJobID(ctx context.Context, id album.AlbumID, userID string, shotIndex int, jobID string) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	a, ok := r.albums[id]
	if !ok || a.UserID != userID {
		return fmt.Errorf("album not found: %s", id)
	}
	if shotIndex < 0 || shotIndex >= len(a.Shots) {
		return fmt.Errorf("shot index out of range: %d", shotIndex)
	}
	a.Shots[shotIndex].JobID = jobID
	a.Shots[shotIndex].Status = album.ShotGenerating
	a.UpdatedAt = time.Now().UTC()
	return nil
}
