package service

import (
	"context"
	"fmt"

	"{{GO_MODULE}}/pkg/album"
	"{{GO_MODULE}}/pkg/logging"
	"{{GO_MODULE}}/pkg/queue"
	"{{GO_MODULE}}/services/{{COMPONENT_NAME}}/internal/port"
)

// AlbumService handles album creation, retrieval, and generation orchestration.
// All generation is async: service enqueues jobs and returns immediately.
// Results arrive via SSE on the user:<userId> channel.
type AlbumService struct {
	albums port.AlbumRepository
	queue  queue.Producer
	logger *logging.Logger
}

// NewAlbumService creates a new AlbumService.
func NewAlbumService(albums port.AlbumRepository, q queue.Producer, logger *logging.Logger) *AlbumService {
	return &AlbumService{
		albums: albums,
		queue:  q,
		logger: logger.WithComponent("AlbumService"),
	}
}

// Create creates a new album with the given shots and persists it.
// Shots are provided as ShotTemplate slices (Label + Direction).
func (s *AlbumService) Create(ctx context.Context, userID, name, subjectDesc string, shots []album.ShotTemplate) (*album.Album, error) {
	if name == "" {
		return nil, fmt.Errorf("album name is required")
	}
	if subjectDesc == "" {
		return nil, fmt.Errorf("subject description is required")
	}
	if len(shots) == 0 {
		return nil, fmt.Errorf("at least one shot is required")
	}
	if len(shots) > 20 {
		return nil, fmt.Errorf("maximum 20 shots per album")
	}

	shotList := make([]album.Shot, len(shots))
	for i, tmpl := range shots {
		shotList[i] = album.Shot{
			Index:     i,
			Label:     tmpl.Label,
			Direction: tmpl.Direction,
			Status:    album.ShotPending,
		}
	}

	a := &album.Album{
		ID:          album.AlbumID("alb_" + generateID()),
		UserID:      userID,
		Name:        name,
		SubjectDesc: subjectDesc,
		Shots:       shotList,
	}

	if err := s.albums.Create(ctx, a); err != nil {
		return nil, fmt.Errorf("create album: %w", err)
	}

	s.logger.Info("album created", "album_id", string(a.ID), "user_id", userID, "shots", len(shotList))
	return a, nil
}

// Get returns an album by ID, enforcing user ownership.
func (s *AlbumService) Get(ctx context.Context, id album.AlbumID, userID string) (*album.Album, error) {
	a, err := s.albums.Get(ctx, id, userID)
	if err != nil {
		return nil, fmt.Errorf("album not found: %w", err)
	}
	return a, nil
}

// List returns all albums for a user.
func (s *AlbumService) List(ctx context.Context, userID string) ([]album.Album, error) {
	return s.albums.List(ctx, userID)
}

// Delete removes an album. Does NOT delete stored images.
func (s *AlbumService) Delete(ctx context.Context, id album.AlbumID, userID string) error {
	return s.albums.Delete(ctx, id, userID)
}

// GenerateAnchor enqueues an anchor generation job for an album.
// Returns the job ID. Result arrives via album_anchor_complete SSE event.
func (s *AlbumService) GenerateAnchor(ctx context.Context, id album.AlbumID, userID string) (string, error) {
	a, err := s.albums.Get(ctx, id, userID)
	if err != nil {
		return "", fmt.Errorf("album not found: %w", err)
	}

	jobID, err := s.queue.Enqueue(ctx, "generate_anchor", map[string]any{
		"albumId":     string(a.ID),
		"userId":      userID,
		"subjectDesc": a.SubjectDesc,
	})
	if err != nil {
		return "", fmt.Errorf("enqueue anchor job: %w", err)
	}

	if err := s.albums.UpdateAnchorJobID(ctx, id, userID, jobID); err != nil {
		s.logger.Warn("failed to persist anchor job ID", "error", err, "album_id", string(id))
	}

	s.logger.Info("anchor generation enqueued", "album_id", string(id), "job_id", jobID)
	return jobID, nil
}

// GenerateAllShots enqueues generation jobs for all pending shots.
// Returns 422 if the album has no anchor yet (shots require an anchor reference).
func (s *AlbumService) GenerateAllShots(ctx context.Context, id album.AlbumID, userID string) ([]string, error) {
	a, err := s.albums.Get(ctx, id, userID)
	if err != nil {
		return nil, fmt.Errorf("album not found: %w", err)
	}
	if a.AnchorURL == "" {
		return nil, fmt.Errorf("anchor must be generated before shots")
	}

	var jobIDs []string
	for _, shot := range a.Shots {
		if shot.Status != album.ShotPending && shot.Status != album.ShotFailed {
			continue
		}
		jobID, err := s.enqueueShotJob(ctx, a, shot.Index)
		if err != nil {
			s.logger.Error("failed to enqueue shot", "error", err, "album_id", string(id), "shot_index", shot.Index)
			continue
		}
		jobIDs = append(jobIDs, jobID)
	}

	s.logger.Info("shots enqueued", "album_id", string(id), "count", len(jobIDs))
	return jobIDs, nil
}

// GenerateShot enqueues a generation job for a single shot (for regeneration).
func (s *AlbumService) GenerateShot(ctx context.Context, id album.AlbumID, userID string, shotIndex int) (string, error) {
	a, err := s.albums.Get(ctx, id, userID)
	if err != nil {
		return "", fmt.Errorf("album not found: %w", err)
	}
	if a.AnchorURL == "" {
		return "", fmt.Errorf("anchor must be generated before shots")
	}
	if shotIndex < 0 || shotIndex >= len(a.Shots) {
		return "", fmt.Errorf("shot index out of range: %d", shotIndex)
	}
	return s.enqueueShotJob(ctx, a, shotIndex)
}

// ResetShot clears a shot back to pending so it can be regenerated.
func (s *AlbumService) ResetShot(ctx context.Context, id album.AlbumID, userID string, shotIndex int) error {
	return s.albums.ResetShot(ctx, id, userID, shotIndex)
}

// enqueueShotJob is the internal helper that enqueues a single shot generation job.
func (s *AlbumService) enqueueShotJob(ctx context.Context, a *album.Album, shotIndex int) (string, error) {
	shot := a.Shots[shotIndex]
	jobID, err := s.queue.Enqueue(ctx, "generate_shot", map[string]any{
		"albumId":     string(a.ID),
		"userId":      a.UserID,
		"shotIndex":   shotIndex,
		"anchorUrl":   a.AnchorURL,
		"subjectDesc": a.SubjectDesc,
		"direction":   shot.Direction,
	})
	if err != nil {
		return "", fmt.Errorf("enqueue shot job: %w", err)
	}

	if err := s.albums.UpdateShotJobID(ctx, a.ID, a.UserID, shotIndex, jobID); err != nil {
		s.logger.Warn("failed to persist shot job ID", "error", err, "shot_index", shotIndex)
	}

	return jobID, nil
}

