package handlers

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/gin-gonic/gin"
	"wa-assistant/backend/database"
	"wa-assistant/backend/models"
	"wa-assistant/backend/services"
)

func simulationProgress(log services.SimulationLog) services.SimulationLog {
	for i := range log.Events {
		data := log.Events[i].Data
		summary := map[string]any{}
		for _, key := range []string{"issue", "error_code", "duration_ms", "response_mode", "max_chars", "model", "input_tokens", "output_tokens", "model_calls", "model_call", "model_call_limit", "response_validated", "response_chars", "handoff_reason", "rating", "note", "truncated"} {
			if v, ok := data[key]; ok {
				summary[key] = v
			}
		}
		log.Events[i].Data = summary
	}
	return log
}

func mayContinueSimulation(c *gin.Context, agentID uint) bool {
	if !mayContinueInboxStream(c, agentID) {
		return false
	}
	if isTenantAdmin(c) {
		return true
	}
	var count int64
	err := database.DB.Model(&models.User{}).Where("id = ? AND tenant_id = ? AND active = ? AND can_manage_ai = ?", currentUserID(c), currentTenantID(c), true, true).Count(&count).Error
	return err == nil && count == 1
}

// SSE carries only allowlisted process metadata, never private reasoning,
// prompts, credentials or tool payloads, even when diagnostic content is on.
func StreamSimulationLog(c *gin.Context) {
	id, ok := resolveAgent(c)
	if !ok {
		return
	}
	updates, unsubscribe, err := simulationLogs.Subscribe(id, c.Param("turnID"))
	if err != nil {
		c.JSON(400, gin.H{"error": "ID pengujian tidak valid."})
		return
	}
	defer unsubscribe()
	log, err := simulationLogs.Read(id, c.Param("turnID"))
	if errors.Is(err, os.ErrNotExist) {
		c.JSON(404, gin.H{"error": "Log uji belum tersedia."})
		return
	}
	if err != nil {
		c.JSON(503, gin.H{"error": "Progres belum dapat dibaca."})
		return
	}
	c.Header("Content-Type", "text/event-stream")
	c.Header("Cache-Control", "no-store")
	c.Header("X-Accel-Buffering", "no")
	c.Status(http.StatusOK)
	write := func(log services.SimulationLog) bool {
		raw, err := json.Marshal(simulationProgress(log))
		if err != nil {
			return false
		}
		// Bound a stalled proxy; it must not retain a handler indefinitely.
		_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Now().Add(10 * time.Second))
		if _, err := fmt.Fprintf(c.Writer, "event: progress\ndata: %s\n\n", raw); err != nil {
			return false
		}
		c.Writer.Flush()
		return true
	}
	defer http.NewResponseController(c.Writer).SetWriteDeadline(time.Time{})
	if !write(log) || log.Status != "running" {
		return
	}
	heartbeat := time.NewTicker(10 * time.Second)
	defer heartbeat.Stop()
	deadline := time.NewTimer(90 * time.Second)
	defer deadline.Stop()
	for {
		select {
		case <-c.Request.Context().Done():
			return
		case <-deadline.C:
			return
		case <-heartbeat.C:
			if !mayContinueSimulation(c, id) {
				return
			}
			_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Now().Add(10 * time.Second))
			if _, err := c.Writer.WriteString(": keepalive\n\n"); err != nil {
				return
			}
			c.Writer.Flush()
		case <-updates:
			log, err = simulationLogs.Read(id, c.Param("turnID"))
			if err != nil || !write(log) || log.Status != "running" {
				return
			}
		}
	}
}
