package api

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/ez-api/ez-api/internal/dto"
	"github.com/ez-api/ez-api/internal/model"
	"github.com/gin-gonic/gin"
)

// CreateBinding godoc
// @Summary      Create a new binding
// @Description  Create a new (namespace, public_model) binding to a provider group and selector
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     AdminAuth
// @Param        binding  body      dto.BindingDTO  true  "Binding Info"
// @Success      201      {object}  model.Binding
// @Failure      400      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /admin/bindings [post]
func (h *Handler) CreateBinding(c *gin.Context) {
	var req dto.BindingDTO
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	ns := strings.TrimSpace(req.Namespace)
	pm := strings.TrimSpace(req.PublicModel)
	if ns == "" || pm == "" || req.GroupID == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "namespace, public_model, and group_id required"})
		return
	}

	if err := h.ensureActiveGroup(req.GroupID); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	st := strings.TrimSpace(req.Status)
	if st == "" {
		st = "active"
	}

	selectorType := strings.TrimSpace(req.SelectorType)
	if selectorType == "" {
		selectorType = "exact"
	}

	b := model.Binding{
		Namespace:     ns,
		PublicModel:   pm,
		GroupID:       req.GroupID,
		Weight:        normalizeWeight(req.Weight),
		SelectorType:  selectorType,
		SelectorValue: strings.TrimSpace(req.SelectorValue),
		Status:        st,
	}

	if err := h.db.Create(&b).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create binding", "details": err.Error()})
		return
	}

	if err := h.sync.SyncBindings(h.db); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, b)
}

// ListBindings godoc
// @Summary      List bindings
// @Description  List all configured bindings
// @Tags         admin
// @Produce      json
// @Security     AdminAuth
// @Param        page    query     int     false  "page (1-based)"
// @Param        limit   query     int     false  "limit (default 50, max 200)"
// @Param        search  query     string  false  "search by namespace/public_model"
// @Success      200  {array}   model.Binding
// @Failure      500  {object}  gin.H
// @Router       /admin/bindings [get]
func (h *Handler) ListBindings(c *gin.Context) {
	var out []model.Binding
	q := h.db.Model(&model.Binding{}).Order("id desc")
	query := parseListQuery(c)
	q = applyListSearch(q, query.Search, "namespace", "public_model")
	q = applyListPagination(q, query)
	if err := q.Find(&out).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list bindings", "details": err.Error()})
		return
	}
	c.JSON(http.StatusOK, out)
}

// UpdateBinding godoc
// @Summary      Update a binding
// @Description  Update an existing binding
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     AdminAuth
// @Param        id       path      int            true  "Binding ID"
// @Param        binding  body      dto.BindingDTO true  "Binding Info"
// @Success      200      {object}  model.Binding
// @Failure      400      {object}  gin.H
// @Failure      404      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /admin/bindings/{id} [put]
func (h *Handler) UpdateBinding(c *gin.Context) {
	idParam := c.Param("id")
	id, err := strconv.Atoi(idParam)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}

	var existing model.Binding
	if err := h.db.First(&existing, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "binding not found"})
		return
	}

	var req dto.BindingDTO
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if ns := strings.TrimSpace(req.Namespace); ns != "" {
		existing.Namespace = ns
	}
	if pm := strings.TrimSpace(req.PublicModel); pm != "" {
		existing.PublicModel = pm
	}
	if req.GroupID != 0 {
		if err := h.ensureActiveGroup(req.GroupID); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		existing.GroupID = req.GroupID
	}
	if req.Weight > 0 {
		existing.Weight = normalizeWeight(req.Weight)
	}
	if st := strings.TrimSpace(req.Status); st != "" {
		existing.Status = st
	}
	if t := strings.TrimSpace(req.SelectorType); t != "" {
		existing.SelectorType = t
	}
	if req.SelectorValue != "" {
		existing.SelectorValue = strings.TrimSpace(req.SelectorValue)
	}

	if err := h.db.Save(&existing).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update binding", "details": err.Error()})
		return
	}

	if err := h.sync.SyncBindings(h.db); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, existing)
}

// GetBinding godoc
// @Summary      Get a binding
// @Description  Get a binding by id
// @Tags         admin
// @Produce      json
// @Security     AdminAuth
// @Param        id   path      int  true  "Binding ID"
// @Success      200  {object}  model.Binding
// @Failure      400  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /admin/bindings/{id} [get]
func (h *Handler) GetBinding(c *gin.Context) {
	idParam := c.Param("id")
	id, err := strconv.Atoi(idParam)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}
	var existing model.Binding
	if err := h.db.First(&existing, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "binding not found"})
		return
	}
	c.JSON(http.StatusOK, existing)
}

// DeleteBinding godoc
// @Summary      Delete a binding
// @Description  Delete a binding by id and rebuild bindings snapshot
// @Tags         admin
// @Produce      json
// @Security     AdminAuth
// @Param        id   path      int  true  "Binding ID"
// @Success      200  {object}  gin.H
// @Failure      400  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /admin/bindings/{id} [delete]
func (h *Handler) DeleteBinding(c *gin.Context) {
	idParam := c.Param("id")
	id, err := strconv.Atoi(idParam)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
		return
	}

	var existing model.Binding
	if err := h.db.First(&existing, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "binding not found"})
		return
	}
	if err := h.db.Delete(&existing).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete binding", "details": err.Error()})
		return
	}

	if err := h.sync.SyncBindings(h.db); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"status": "deleted"})
}

func normalizeWeight(weight int) int {
	if weight <= 0 {
		return 1
	}
	return weight
}

func (h *Handler) ensureActiveGroup(groupID uint) error {
	var group model.ProviderGroup
	if err := h.db.First(&group, groupID).Error; err != nil {
		return fmt.Errorf("provider group not found")
	}
	if strings.TrimSpace(group.Status) != "" && strings.TrimSpace(group.Status) != "active" {
		return fmt.Errorf("provider group not active")
	}
	var count int64
	if err := h.db.Model(&model.APIKey{}).
		Where("group_id = ? AND status = ?", groupID, "active").
		Count(&count).Error; err != nil {
		return fmt.Errorf("failed to check api keys")
	}
	if count == 0 {
		return fmt.Errorf("provider group has no active api keys")
	}
	return nil
}
