package controllers

import (
	"encoding/json"
	"strconv"
	"strings"
	"time"

	"doormile/db"
	"doormile/models"
	"doormile/utils"

	"github.com/gofiber/fiber/v2"
)

// POST /api/v1/internal/agent-decisions
func CreateAgentDecision(c *fiber.Ctx) error {
	type req struct {
		DecisionType     string                 `json:"decision_type"`
		BookingID        *uint64                `json:"booking_id"`
		Context          map[string]interface{} `json:"context"`
		Decision         map[string]interface{} `json:"decision"`
		Reasoning        string                 `json:"reasoning"`
		ContextEmbedding []float32              `json:"context_embedding"`
	}

	body := new(req)
	if err := c.BodyParser(body); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if body.DecisionType == "" {
		return utils.BadRequest(c, "decision_type is required")
	}

	contextJSON, err := json.Marshal(body.Context)
	if err != nil {
		return utils.BadRequest(c, "invalid context JSON")
	}
	decisionJSON, err := json.Marshal(body.Decision)
	if err != nil {
		return utils.BadRequest(c, "invalid decision JSON")
	}

	record := models.AgentDecision{
		DecisionType: body.DecisionType,
		BookingID:    body.BookingID,
		Context:      string(contextJSON),
		Decision:     string(decisionJSON),
		Reasoning:    body.Reasoning,
		CreatedAt:    time.Now(),
	}

	if err := db.DB.Create(&record).Error; err != nil {
		utils.Error("CreateAgentDecision: insert failed", "error", err)
		return utils.Internal(c, "failed to create agent decision")
	}

	if len(body.ContextEmbedding) > 0 {
		parts := make([]string, len(body.ContextEmbedding))
		for i, v := range body.ContextEmbedding {
			parts[i] = strconv.FormatFloat(float64(v), 'f', -1, 32)
		}
		embeddingStr := "[" + strings.Join(parts, ",") + "]"
		if err := db.DB.Exec(
			"UPDATE agent_decisions SET context_embedding = ? WHERE id = ?",
			embeddingStr, record.ID,
		).Error; err != nil {
			utils.Warn("CreateAgentDecision: embedding update failed", "id", record.ID, "error", err)
		}
	}

	return utils.Created(c, fiber.Map{"id": record.ID})
}

// GET /api/v1/internal/agent-decisions/similar
func FindSimilarDecisions(c *fiber.Ctx) error {
	decisionType := c.Query("decision_type")
	limit, err := strconv.Atoi(c.Query("limit", "5"))
	if err != nil || limit < 1 {
		limit = 5
	}

	type req struct {
		Embedding []float32 `json:"embedding"`
	}
	body := new(req)
	if err := c.BodyParser(body); err != nil || len(body.Embedding) == 0 {
		return utils.BadRequest(c, "embedding array is required")
	}

	parts := make([]string, len(body.Embedding))
	for i, v := range body.Embedding {
		parts[i] = strconv.FormatFloat(float64(v), 'f', -1, 32)
	}
	embeddingStr := "[" + strings.Join(parts, ",") + "]"

	type row struct {
		Decision  string  `json:"decision"`
		Reasoning string  `json:"reasoning"`
		Outcome   *string `json:"outcome"`
		Distance  float64 `json:"distance"`
	}
	var results []row

	if err := db.DB.Raw(
		`SELECT decision, reasoning, outcome,
		        context_embedding <=> ? AS distance
		 FROM agent_decisions
		 WHERE decision_type = ? AND outcome IS NOT NULL
		 ORDER BY context_embedding <=> ?
		 LIMIT ?`,
		embeddingStr, decisionType, embeddingStr, limit,
	).Scan(&results).Error; err != nil {
		utils.Error("FindSimilarDecisions: query failed", "error", err)
		return utils.Internal(c, "failed to query similar decisions")
	}

	return utils.List(c, results, int64(len(results)))
}

// PATCH /api/v1/internal/agent-decisions/:id/outcome
func UpdateDecisionOutcome(c *fiber.Ctx) error {
	id, err := strconv.ParseUint(c.Params("id"), 10, 64)
	if err != nil {
		return utils.BadRequest(c, "invalid decision ID")
	}

	type req struct {
		Outcome string `json:"outcome"`
	}
	body := new(req)
	if err := c.BodyParser(body); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if body.Outcome == "" {
		return utils.BadRequest(c, "outcome is required")
	}

	now := time.Now()
	result := db.DB.Model(&models.AgentDecision{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"outcome":             body.Outcome,
			"outcome_recorded_at": now,
		})

	if result.Error != nil {
		utils.Error("UpdateDecisionOutcome: update failed", "id", id, "error", result.Error)
		return utils.Internal(c, "failed to update outcome")
	}
	if result.RowsAffected == 0 {
		return utils.NotFound(c, "agent decision not found")
	}

	return utils.OK(c, fiber.Map{"id": id, "outcome": body.Outcome})
}
