package handler

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"

	"github.com/warmbly/warmbly/internal/api/middleware"
	"github.com/warmbly/warmbly/internal/errx"
	"github.com/warmbly/warmbly/internal/models"
)

// warmupRoutingRulePayload is the wire shape for create/update requests.
// Kept separate from models.WarmupRoutingRule so server-managed fields
// (id, organization_id, created_at, updated_at) cannot be set by clients.
type warmupRoutingRulePayload struct {
	Name                string  `json:"name"`
	Priority            int     `json:"priority"`
	SenderMatchType     string  `json:"sender_match_type"`
	SenderMatchValue    string  `json:"sender_match_value"`
	RecipientMatchType  string  `json:"recipient_match_type"`
	RecipientMatchValue string  `json:"recipient_match_value"`
	Weight              float64 `json:"weight"`
	Enabled             bool    `json:"enabled"`
}

func (p *warmupRoutingRulePayload) toModel(orgID, ruleID uuid.UUID) *models.WarmupRoutingRule {
	return &models.WarmupRoutingRule{
		ID:                  ruleID,
		OrganizationID:      orgID,
		Name:                strings.TrimSpace(p.Name),
		Priority:            p.Priority,
		SenderMatchType:     models.WarmupRoutingMatchType(p.SenderMatchType),
		SenderMatchValue:    strings.ToLower(strings.TrimSpace(p.SenderMatchValue)),
		RecipientMatchType:  models.WarmupRoutingMatchType(p.RecipientMatchType),
		RecipientMatchValue: strings.ToLower(strings.TrimSpace(p.RecipientMatchValue)),
		Weight:              p.Weight,
		Enabled:             p.Enabled,
	}
}

func validateRoutingPayload(p *warmupRoutingRulePayload) *errx.Error {
	if strings.TrimSpace(p.Name) == "" {
		return errx.New(errx.BadRequest, "name is required")
	}
	if !isValidMatchType(p.SenderMatchType) {
		return errx.New(errx.BadRequest, "invalid sender_match_type")
	}
	if !isValidMatchType(p.RecipientMatchType) {
		return errx.New(errx.BadRequest, "invalid recipient_match_type")
	}
	if p.SenderMatchType != string(models.WarmupMatchAny) && p.SenderMatchValue == "" {
		return errx.New(errx.BadRequest, "sender_match_value is required for non-any match types")
	}
	if p.RecipientMatchType != string(models.WarmupMatchAny) && p.RecipientMatchValue == "" {
		return errx.New(errx.BadRequest, "recipient_match_value is required for non-any match types")
	}
	if p.Weight < 0 {
		return errx.New(errx.BadRequest, "weight must be >= 0")
	}
	return nil
}

func isValidMatchType(t string) bool {
	switch models.WarmupRoutingMatchType(t) {
	case models.WarmupMatchAny, models.WarmupMatchDomain, models.WarmupMatchTLD, models.WarmupMatchProvider:
		return true
	}
	return false
}

// ListWarmupRoutingRules returns every rule for the caller's organization,
// ordered by priority (ascending — first to evaluate).
func (h *Handler) ListWarmupRoutingRules(c *gin.Context) {
	orgID, ok := requireOrgID(c)
	if !ok {
		return
	}
	rules, err := h.WarmupRoutingRepo.ListForOrganization(c.Request.Context(), orgID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list rules"})
		return
	}
	if rules == nil {
		rules = []models.WarmupRoutingRule{}
	}
	c.JSON(http.StatusOK, gin.H{"rules": rules})
}

// CreateWarmupRoutingRule creates a new rule for the caller's organization.
func (h *Handler) CreateWarmupRoutingRule(c *gin.Context) {
	orgID, ok := requireOrgID(c)
	if !ok {
		return
	}
	var payload warmupRoutingRulePayload
	if err := c.ShouldBindJSON(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
		return
	}
	if xerr := validateRoutingPayload(&payload); xerr != nil {
		errx.JSON(c, xerr)
		return
	}
	rule := payload.toModel(orgID, uuid.New())
	if err := h.WarmupRoutingRepo.Create(c.Request.Context(), rule); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create rule"})
		return
	}

	ruleID := rule.ID
	h.auditOrg(c, models.AuditActionCreate, models.AuditEntityWarmupRoutingRule, &ruleID, nil, map[string]string{
		"name": rule.Name,
	})

	c.JSON(http.StatusCreated, rule)
}

// UpdateWarmupRoutingRule updates a rule by ID.
func (h *Handler) UpdateWarmupRoutingRule(c *gin.Context) {
	orgID, ok := requireOrgID(c)
	if !ok {
		return
	}
	ruleID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid rule id"})
		return
	}
	var payload warmupRoutingRulePayload
	if err := c.ShouldBindJSON(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
		return
	}
	if xerr := validateRoutingPayload(&payload); xerr != nil {
		errx.JSON(c, xerr)
		return
	}
	rule := payload.toModel(orgID, ruleID)
	if err := h.WarmupRoutingRepo.Update(c.Request.Context(), rule); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}

	h.auditOrg(c, models.AuditActionUpdate, models.AuditEntityWarmupRoutingRule, &ruleID, nil, map[string]string{
		"name": rule.Name,
	})

	c.JSON(http.StatusOK, rule)
}

// DeleteWarmupRoutingRule removes a rule by ID.
func (h *Handler) DeleteWarmupRoutingRule(c *gin.Context) {
	orgID, ok := requireOrgID(c)
	if !ok {
		return
	}
	ruleID, err := uuid.Parse(c.Param("id"))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid rule id"})
		return
	}
	if err := h.WarmupRoutingRepo.Delete(c.Request.Context(), orgID, ruleID); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		return
	}

	h.auditOrg(c, models.AuditActionDelete, models.AuditEntityWarmupRoutingRule, &ruleID, nil, nil)

	c.Status(http.StatusNoContent)
}

// requireOrgID resolves the caller's organization ID via the auth middleware
// helper, writing a 403 and returning ok=false when no org is set.
func requireOrgID(c *gin.Context) (uuid.UUID, bool) {
	if orgID := middleware.GetOrganizationID(c); orgID != nil {
		return *orgID, true
	}
	errx.JSON(c, errx.New(errx.Forbidden, "organization context required"))
	return uuid.Nil, false
}
