package handlers

import (
	"errors"
	"net/http"
	"strconv"

	"github.com/go-chi/chi/v5"

	"git.threesix.ai/jordan/persona-community-5/pkg/album"
	"git.threesix.ai/jordan/persona-community-5/pkg/app"
	"git.threesix.ai/jordan/persona-community-5/pkg/auth"
	"git.threesix.ai/jordan/persona-community-5/pkg/httperror"
	"git.threesix.ai/jordan/persona-community-5/pkg/httpresponse"
	"git.threesix.ai/jordan/persona-community-5/pkg/logging"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/service"
)

// Album handles HTTP requests for album CRUD and generation endpoints.
// All generation endpoints are async: they enqueue a job and return 202.
// Results arrive via SSE events on the user:<userId> channel.
type Album struct {
	albums *service.AlbumService
	logger *logging.Logger
}

// NewAlbum creates a new Album handler.
func NewAlbum(albums *service.AlbumService, logger *logging.Logger) *Album {
	return &Album{
		albums: albums,
		logger: logger.WithComponent("AlbumHandler"),
	}
}

// ---------------------------------------------------------------------------
// Request/response types
// ---------------------------------------------------------------------------

// CreateAlbumRequest is the request body for POST /albums.
type CreateAlbumRequest struct {
	Name        string             `json:"name" validate:"required,min=1,max=100"`
	SubjectDesc string             `json:"subjectDesc" validate:"required,min=1,max=500"`
	Shots       []ShotTemplateBody `json:"shots" validate:"required,min=1,max=20"`
	TemplateSet string             `json:"templateSet"` // Optional: "portrait", "product", "character"
}

// ShotTemplateBody is a single shot spec in the create request.
type ShotTemplateBody struct {
	Label     string `json:"label" validate:"required"`
	Direction string `json:"direction" validate:"required"`
}

// AlbumJobResponse is the response for generation enqueue endpoints.
type AlbumJobResponse struct {
	JobID string `json:"jobId"`
}

// AlbumJobsResponse is the response for bulk generation enqueue.
type AlbumJobsResponse struct {
	JobIDs []string `json:"jobIds"`
}

// ---------------------------------------------------------------------------
// CRUD
// ---------------------------------------------------------------------------

// Create handles POST /albums — creates a new album with shot specs.
func (h *Album) Create(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	var req CreateAlbumRequest
	if err := app.BindAndValidate(r, &req); err != nil {
		return err
	}

	// If a template set was provided, use it (overrides explicit shots).
	var shots []album.ShotTemplate
	if req.TemplateSet != "" {
		set, ok := album.ShotTemplateSets[req.TemplateSet]
		if !ok {
			return httperror.BadRequest("unknown template set: " + req.TemplateSet)
		}
		shots = set
	} else {
		// Convert body shots to ShotTemplate.
		shots = make([]album.ShotTemplate, len(req.Shots))
		for i, s := range req.Shots {
			shots[i] = album.ShotTemplate{Label: s.Label, Direction: s.Direction}
		}
	}

	a, err := h.albums.Create(r.Context(), user.ID, req.Name, req.SubjectDesc, shots)
	if err != nil {
		h.logger.Error("failed to create album", "error", err, "user_id", user.ID)
		return httperror.BadRequest(err.Error())
	}

	httpresponse.Created(w, r, a)
	return nil
}

// List handles GET /albums — returns all albums for the authenticated user.
func (h *Album) List(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	albums, err := h.albums.List(r.Context(), user.ID)
	if err != nil {
		h.logger.Error("failed to list albums", "error", err, "user_id", user.ID)
		return httperror.Internal("failed to list albums")
	}

	if albums == nil {
		albums = []album.Album{}
	}

	httpresponse.OK(w, r, albums)
	return nil
}

// Get handles GET /albums/{id} — returns a single album with all shot statuses.
func (h *Album) Get(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	id := album.AlbumID(chi.URLParam(r, "id"))
	if id == "" {
		return httperror.BadRequest("album ID is required")
	}

	a, err := h.albums.Get(r.Context(), id, user.ID)
	if err != nil {
		return httperror.NotFound("album not found")
	}

	httpresponse.OK(w, r, a)
	return nil
}

// Delete handles DELETE /albums/{id} — deletes an album.
func (h *Album) Delete(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	id := album.AlbumID(chi.URLParam(r, "id"))
	if id == "" {
		return httperror.BadRequest("album ID is required")
	}

	if err := h.albums.Delete(r.Context(), id, user.ID); err != nil {
		return httperror.NotFound("album not found")
	}

	httpresponse.NoContent(w)
	return nil
}

// ---------------------------------------------------------------------------
// Generation (async — returns 202)
// ---------------------------------------------------------------------------

// GenerateAnchor handles POST /albums/{id}/anchor — enqueues anchor generation.
// Returns 202 with job ID. Result arrives via album_anchor_complete SSE event.
func (h *Album) GenerateAnchor(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	id := album.AlbumID(chi.URLParam(r, "id"))
	if id == "" {
		return httperror.BadRequest("album ID is required")
	}

	jobID, err := h.albums.GenerateAnchor(r.Context(), id, user.ID)
	if err != nil {
		h.logger.Error("failed to enqueue anchor job", "error", err, "album_id", string(id))
		return httperror.NotFound("album not found")
	}

	h.logger.Info("anchor generation enqueued", "album_id", string(id), "job_id", jobID)
	httpresponse.Accepted(w, r, AlbumJobResponse{JobID: jobID})
	return nil
}

// GenerateAllShots handles POST /albums/{id}/shots — enqueues all pending shots.
// Returns 422 if the album has no anchor yet.
// Returns 202 with job IDs for all enqueued shots.
func (h *Album) GenerateAllShots(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	id := album.AlbumID(chi.URLParam(r, "id"))
	if id == "" {
		return httperror.BadRequest("album ID is required")
	}

	jobIDs, err := h.albums.GenerateAllShots(r.Context(), id, user.ID)
	if err != nil {
		if errors.Is(err, album.ErrAnchorRequired) {
			return httperror.UnprocessableEntity("anchor must be generated before shots")
		}
		return httperror.NotFound("album not found")
	}

	if jobIDs == nil {
		jobIDs = []string{}
	}

	h.logger.Info("shots enqueued", "album_id", string(id), "count", len(jobIDs))
	httpresponse.Accepted(w, r, AlbumJobsResponse{JobIDs: jobIDs})
	return nil
}

// GenerateShot handles POST /albums/{id}/shots/{index} — enqueues a single shot (for regeneration).
func (h *Album) GenerateShot(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	id := album.AlbumID(chi.URLParam(r, "id"))
	if id == "" {
		return httperror.BadRequest("album ID is required")
	}

	shotIndex, err := parseShotIndex(chi.URLParam(r, "index"))
	if err != nil {
		return httperror.BadRequest("shot index must be a non-negative integer")
	}

	jobID, err := h.albums.GenerateShot(r.Context(), id, user.ID, shotIndex)
	if err != nil {
		if errors.Is(err, album.ErrAnchorRequired) {
			return httperror.UnprocessableEntity("anchor must be generated before shots")
		}
		return httperror.NotFound("album or shot not found")
	}

	httpresponse.Accepted(w, r, AlbumJobResponse{JobID: jobID})
	return nil
}

// ResetShot handles DELETE /albums/{id}/shots/{index} — resets a shot to pending.
func (h *Album) ResetShot(w http.ResponseWriter, r *http.Request) error {
	user := auth.GetUser(r.Context())
	if user == nil {
		return httperror.Unauthorized("authentication required")
	}

	id := album.AlbumID(chi.URLParam(r, "id"))
	if id == "" {
		return httperror.BadRequest("album ID is required")
	}

	shotIndex, err := parseShotIndex(chi.URLParam(r, "index"))
	if err != nil {
		return httperror.BadRequest("shot index must be a non-negative integer")
	}

	if err := h.albums.ResetShot(r.Context(), id, user.ID, shotIndex); err != nil {
		return httperror.NotFound("album or shot not found")
	}

	httpresponse.NoContent(w)
	return nil
}

// parseShotIndex parses and validates the shot index URL parameter.
// Returns an error if the value is missing, non-numeric, or negative.
func parseShotIndex(idx string) (int, error) {
	if idx == "" {
		return 0, errors.New("missing shot index")
	}
	n, err := strconv.Atoi(idx)
	if err != nil || n < 0 {
		return 0, errors.New("shot index must be a non-negative integer")
	}
	return n, nil
}
