package handlers

import (
	"fmt"
	"net/http"
	"time"

	"github.com/flatrender/render-svc/internal/db"
	"github.com/flatrender/render-svc/internal/models"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/minio/minio-go/v7"
)

// SnapshotJobHandler queues per-scene single-frame AE snapshot jobs and exposes
// the node-facing claim/result/fail lifecycle. On result it writes the image URL
// onto content.scenes.snapshot_url (in the store, cross-schema).
type SnapshotJobHandler struct {
	store           *db.Store
	minio           *minio.Client
	templatesBucket string
	uploadBucket    string // public-read bucket snapshots land in (e.g. user-uploads)
	publicBase      string // browser-reachable base, e.g. http://172.28.144.1:9000
}

func NewSnapshotJobHandler(store *db.Store, mc *minio.Client, templatesBucket, uploadBucket, publicBase string) *SnapshotJobHandler {
	return &SnapshotJobHandler{store: store, minio: mc, templatesBucket: templatesBucket, uploadBucket: uploadBucket, publicBase: publicBase}
}

// POST /v1/scene-snapshots/:project_id  (admin) → queue one job per active scene.
func (h *SnapshotJobHandler) Enqueue(c *gin.Context) {
	pid, err := uuid.Parse(c.Param("project_id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid project_id"})
		return
	}
	n, err := h.store.EnqueueSceneSnapshots(c.Request.Context(), pid)
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"enqueued": n})
}

// GET /v1/scene-snapshots/:project_id  (admin) → job statuses for polling.
func (h *SnapshotJobHandler) List(c *gin.Context) {
	pid, err := uuid.Parse(c.Param("project_id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid project_id"})
		return
	}
	jobs, err := h.store.ListSnapshotJobs(c.Request.Context(), pid)
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()})
		return
	}
	if jobs == nil {
		jobs = []db.SnapshotJob{}
	}
	c.JSON(http.StatusOK, gin.H{"jobs": jobs})
}

// POST /v1/internal/snapshot/claim  (node, HMAC)
func (h *SnapshotJobHandler) Claim(c *gin.Context) {
	var req models.ClaimJobRequest
	_ = c.ShouldBindJSON(&req)

	claim, err := h.store.ClaimSnapshotJob(c.Request.Context(), req.NodeID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()})
		return
	}
	if claim == nil {
		c.Status(http.StatusNoContent)
		return
	}
	url, isBundle, md5 := resolveTemplateObject(h.minio, h.templatesBucket, claim.ProjectID)
	if url == "" {
		_ = h.store.SetSnapshotError(c.Request.Context(), claim.ID,
			"no template stored for this project — upload the .aep from «فایل‌ها» first")
		c.Status(http.StatusNoContent)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"snapshot_job_id":  claim.ID,
		"project_id":       claim.ProjectID,
		"scene_id":         claim.SceneID,
		"scene_key":        claim.SceneKey,
		"comp_name":        claim.CompName,
		"frame":            claim.Frame,
		"aep_download_url": url,
		"is_bundle":        isBundle,
		"bundle_md5":       md5,
	})
}

// POST /v1/internal/snapshot/:id/upload-url  (node, HMAC)
// Presigns a PUT to the public-read uploads bucket and returns the permanent
// public URL the node should report back once the still is uploaded.
func (h *SnapshotJobHandler) UploadURL(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid id"})
		return
	}
	pid, sceneKey, merr := h.store.GetSnapshotJobMeta(c.Request.Context(), id)
	if merr != nil {
		c.JSON(http.StatusNotFound, models.APIError{Code: "not_found", Message: "snapshot job not found"})
		return
	}
	objectKey := fmt.Sprintf("snapshots/%s/%s.png", pid, sceneKey)
	put, perr := h.minio.PresignedPutObject(c.Request.Context(), h.uploadBucket, objectKey, 15*time.Minute)
	if perr != nil {
		c.JSON(http.StatusInternalServerError, models.APIError{Code: "presign_failed", Message: perr.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"upload_url": put.String(),
		"object_key": objectKey,
		"public_url": fmt.Sprintf("%s/%s/%s", h.publicBase, h.uploadBucket, objectKey),
	})
}

// POST /v1/internal/snapshot/:id/result  (node, HMAC)  body {image_url}
func (h *SnapshotJobHandler) Result(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid id"})
		return
	}
	var req struct {
		ImageURL string `json:"image_url"`
	}
	if err := c.ShouldBindJSON(&req); err != nil || req.ImageURL == "" {
		c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "image_url required"})
		return
	}
	if err := h.store.SetSnapshotResult(c.Request.Context(), id, req.ImageURL); err != nil {
		c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()})
		return
	}
	c.Status(http.StatusNoContent)
}

// POST /v1/internal/snapshot/:id/fail  (node, HMAC)  body {reason}
func (h *SnapshotJobHandler) Fail(c *gin.Context) {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, models.APIError{Code: "bad_request", Message: "invalid id"})
		return
	}
	var req struct {
		Reason string `json:"reason"`
	}
	_ = c.ShouldBindJSON(&req)
	if req.Reason == "" {
		req.Reason = "snapshot failed"
	}
	if err := h.store.SetSnapshotError(c.Request.Context(), id, req.Reason); err != nil {
		c.JSON(http.StatusInternalServerError, models.APIError{Code: "internal_error", Message: err.Error()})
		return
	}
	c.Status(http.StatusNoContent)
}
