package replicate

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/rose_cat707/luminary/internal/model"
	"github.com/rose_cat707/luminary/internal/prediction"
)

type Handler struct {
	svc     *prediction.Service
	baseURL func() string
}

func New(svc *prediction.Service, baseURL func() string) *Handler {
	return &Handler{svc: svc, baseURL: baseURL}
}

func (h *Handler) Register(r *gin.RouterGroup) {
	r.POST("/predictions", h.Create)
	r.GET("/predictions", h.List)
	r.GET("/predictions/:id", h.Get)
	r.POST("/predictions/:id/cancel", h.Cancel)
	r.GET("/predictions/:id/stream", h.Stream)
}

func (h *Handler) ingress(c *gin.Context) (*model.IngressKey, bool) {
	v, ok := c.Get("ingress_key")
	if !ok {
		return nil, false
	}
	k, ok := v.(*model.IngressKey)
	return k, ok
}

func (h *Handler) Create(c *gin.Context) {
	ingress, ok := h.ingress(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
		return
	}
	var req prediction.CreateRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"detail": "invalid request body"})
		return
	}
	wait := strings.Contains(c.GetHeader("Prefer"), "wait")
	p, err := h.svc.Create(c.Request.Context(), ingress, req, c.ClientIP(), wait)
	if err != nil {
		code := http.StatusBadRequest
		if strings.Contains(err.Error(), "not found") {
			code = http.StatusNotFound
		}
		c.JSON(code, gin.H{"detail": err.Error()})
		return
	}
	c.JSON(http.StatusCreated, h.svc.APIView(p, h.baseURL()))
}

func (h *Handler) Get(c *gin.Context) {
	ingress, ok := h.ingress(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
		return
	}
	p, err := h.svc.Get(c.Request.Context(), c.Param("id"), ingress)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
		return
	}
	c.JSON(http.StatusOK, h.svc.APIView(p, h.baseURL()))
}

func (h *Handler) List(c *gin.Context) {
	ingress, ok := h.ingress(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
		return
	}
	items, err := h.svc.List(c.Request.Context(), ingress, c.Query("cursor"), 100)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
		return
	}
	results := make([]map[string]any, 0, len(items))
	for _, p := range items {
		results = append(results, h.svc.APIView(p, h.baseURL()))
	}
	c.JSON(http.StatusOK, gin.H{"results": results})
}

func (h *Handler) Cancel(c *gin.Context) {
	ingress, ok := h.ingress(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
		return
	}
	p, err := h.svc.Cancel(c.Request.Context(), c.Param("id"), ingress)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
		return
	}
	c.JSON(http.StatusOK, h.svc.APIView(p, h.baseURL()))
}

func (h *Handler) Stream(c *gin.Context) {
	ingress, ok := h.ingress(c)
	if !ok {
		c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
		return
	}
	id := c.Param("id")
	if _, err := h.svc.Get(c.Request.Context(), id, ingress); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
		return
	}

	c.Header("Content-Type", "text/event-stream")
	c.Header("Cache-Control", "no-cache")
	c.Header("Connection", "keep-alive")
	c.Status(http.StatusOK)

	flusher, ok := c.Writer.(http.Flusher)
	if !ok {
		return
	}
	w := bufio.NewWriter(c.Writer)
	ch := h.svc.Hub().Subscribe(id)
	defer h.svc.Hub().Unsubscribe(id, ch)

	for {
		select {
		case <-c.Request.Context().Done():
			return
		case ev, open := <-ch:
			if !open {
				return
			}
			payload, _ := json.Marshal(map[string]any{
				"id": id, "status": ev.Status, "logs": ev.Logs,
				"metrics": ev.Metrics, "output": ev.Output, "error": ev.Error,
			})
			_, _ = fmt.Fprintf(w, "data: %s\n\n", payload)
			w.Flush()
			flusher.Flush()
			if ev.Done {
				_, _ = fmt.Fprintf(w, "event: done\ndata: {}\n\n")
				w.Flush()
				flusher.Flush()
				return
			}
		}
	}
}
