package api

import (
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/ez-api/ez-api/internal/model"
	"github.com/ez-api/ez-api/internal/service"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type MasterHandler struct {
	db             *gorm.DB
	logDB          *gorm.DB
	masterService  *service.MasterService
	syncService    *service.SyncService
	statsService   *service.StatsService
	logPartitioner *service.LogPartitioner
}

func NewMasterHandler(db *gorm.DB, logDB *gorm.DB, masterService *service.MasterService, syncService *service.SyncService, statsService *service.StatsService, partitioner *service.LogPartitioner) *MasterHandler {
	if logDB == nil {
		logDB = db
	}
	return &MasterHandler{db: db, logDB: logDB, masterService: masterService, syncService: syncService, statsService: statsService, logPartitioner: partitioner}
}

func (h *MasterHandler) logDBConn() *gorm.DB {
	if h == nil || h.logDB == nil {
		return h.db
	}
	return h.logDB
}

func (h *MasterHandler) logBaseQuery() *gorm.DB {
	return logBaseQuery(h.logDBConn(), h.logPartitioner)
}

type IssueChildKeyRequest struct {
	Group              string     `json:"group"`
	Scopes             string     `json:"scopes"`
	ModelLimits        string     `json:"model_limits,omitempty"`
	ModelLimitsEnabled *bool      `json:"model_limits_enabled,omitempty"`
	ExpiresAt          *time.Time `json:"expires_at,omitempty"`
	AllowIPs           string     `json:"allow_ips,omitempty"`
	DenyIPs            string     `json:"deny_ips,omitempty"`
}

// IssueChildKey godoc
// @Summary      Issue a child key
// @Description  Issue a new access token (child key) for the authenticated master
// @Tags         master
// @Accept       json
// @Produce      json
// @Security     MasterAuth
// @Param        request  body      IssueChildKeyRequest  true  "Key Request"
// @Success      201      {object}  gin.H
// @Failure      400      {object}  gin.H
// @Failure      401      {object}  gin.H
// @Failure      403      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /v1/tokens [post]
func (h *MasterHandler) IssueChildKey(c *gin.Context) {
	master, exists := c.Get("master")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"})
		return
	}
	masterModel := master.(*model.Master)

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

	// If group is not specified, inherit from master
	group := req.Group
	if strings.TrimSpace(group) == "" {
		group = masterModel.Group
	}

	// Security: Ensure the requested group is allowed for this master.
	// For now, we'll just enforce it's the same group.
	if group != masterModel.Group {
		c.JSON(http.StatusForbidden, gin.H{"error": "cannot issue key for a different group"})
		return
	}

	modelLimits := strings.TrimSpace(req.ModelLimits)
	modelLimitsEnabled := false
	if req.ModelLimitsEnabled != nil {
		modelLimitsEnabled = *req.ModelLimitsEnabled
	} else if modelLimits != "" {
		modelLimitsEnabled = true
	}

	key, rawChildKey, err := h.masterService.IssueChildKey(masterModel.ID, service.IssueKeyOptions{
		Group:              group,
		Scopes:             strings.TrimSpace(req.Scopes),
		ModelLimits:        modelLimits,
		ModelLimitsEnabled: modelLimitsEnabled,
		ExpiresAt:          req.ExpiresAt,
		AllowIPs:           strings.TrimSpace(req.AllowIPs),
		DenyIPs:            strings.TrimSpace(req.DenyIPs),
	})
	if err != nil {
		switch {
		case errors.Is(err, service.ErrMasterNotFound):
			c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
		case errors.Is(err, service.ErrMasterNotActive):
			c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		case errors.Is(err, service.ErrModelLimitForbidden):
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		case errors.Is(err, service.ErrChildKeyGroupForbidden):
			c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
		case errors.Is(err, service.ErrChildKeyLimitReached):
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		default:
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to issue child key", "details": err.Error()})
		}
		return
	}

	if err := h.syncService.SyncKey(key); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync child key", "details": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"id":         key.ID,
		"key_secret": rawChildKey,
		"group":      key.Group,
		"scopes":     key.Scopes,
	})
}

// GetSelf godoc
// @Summary      Get current master info
// @Description  Returns master metadata for the authenticated master key
// @Tags         master
// @Produce      json
// @Security     MasterAuth
// @Success      200  {object}  MasterView
// @Failure      401  {object}  gin.H
// @Router       /v1/self [get]
func (h *MasterHandler) GetSelf(c *gin.Context) {
	master, exists := c.Get("master")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"})
		return
	}
	m := master.(*model.Master)
	c.JSON(http.StatusOK, toMasterView(*m))
}

type TokenView struct {
	ID                 uint       `json:"id"`
	Group              string     `json:"group"`
	Scopes             string     `json:"scopes"`
	Status             string     `json:"status"`
	IssuedBy           string     `json:"issued_by"`
	IssuedAtEpoch      int64      `json:"issued_at_epoch"`
	DefaultNamespace   string     `json:"default_namespace"`
	Namespaces         string     `json:"namespaces"`
	ModelLimits        string     `json:"model_limits,omitempty"`
	ModelLimitsEnabled bool       `json:"model_limits_enabled"`
	ExpiresAt          *time.Time `json:"expires_at,omitempty"`
	AllowIPs           string     `json:"allow_ips,omitempty"`
	DenyIPs            string     `json:"deny_ips,omitempty"`
	LastAccessedAt     *time.Time `json:"last_accessed_at,omitempty"`
	RequestCount       int64      `json:"request_count"`
	UsedTokens         int64      `json:"used_tokens"`
	QuotaLimit         int64      `json:"quota_limit"`
	QuotaUsed          int64      `json:"quota_used"`
	QuotaResetAt       *time.Time `json:"quota_reset_at,omitempty"`
	QuotaResetType     string     `json:"quota_reset_type,omitempty"`
	CreatedAt          int64      `json:"created_at"`
	UpdatedAt          int64      `json:"updated_at"`
}

func toTokenView(k model.Key) TokenView {
	return TokenView{
		ID:                 k.ID,
		Group:              k.Group,
		Scopes:             k.Scopes,
		Status:             k.Status,
		IssuedBy:           k.IssuedBy,
		IssuedAtEpoch:      k.IssuedAtEpoch,
		DefaultNamespace:   strings.TrimSpace(k.DefaultNamespace),
		Namespaces:         strings.TrimSpace(k.Namespaces),
		ModelLimits:        strings.TrimSpace(k.ModelLimits),
		ModelLimitsEnabled: k.ModelLimitsEnabled,
		ExpiresAt:          k.ExpiresAt,
		AllowIPs:           strings.TrimSpace(k.AllowIPs),
		DenyIPs:            strings.TrimSpace(k.DenyIPs),
		LastAccessedAt:     k.LastAccessedAt,
		RequestCount:       k.RequestCount,
		UsedTokens:         k.UsedTokens,
		QuotaLimit:         k.QuotaLimit,
		QuotaUsed:          k.QuotaUsed,
		QuotaResetAt:       k.QuotaResetAt,
		QuotaResetType:     strings.TrimSpace(k.QuotaResetType),
		CreatedAt:          k.CreatedAt.UTC().Unix(),
		UpdatedAt:          k.UpdatedAt.UTC().Unix(),
	}
}

// ListTokens godoc
// @Summary      List child keys
// @Description  List child keys issued under the authenticated master
// @Tags         master
// @Produce      json
// @Security     MasterAuth
// @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 group/scopes/namespaces/status"
// @Success      200  {array}   TokenView
// @Failure      401  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /v1/tokens [get]
func (h *MasterHandler) ListTokens(c *gin.Context) {
	master, exists := c.Get("master")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"})
		return
	}
	m := master.(*model.Master)

	var keys []model.Key
	q := h.db.Model(&model.Key{}).Where("master_id = ?", m.ID).Order("id desc")
	query := parseListQuery(c)
	q = applyListSearch(q, query.Search, `"group"`, "scopes", "default_namespace", "namespaces", "status")
	q = applyListPagination(q, query)
	if err := q.Find(&keys).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list tokens", "details": err.Error()})
		return
	}
	out := make([]TokenView, 0, len(keys))
	for _, k := range keys {
		out = append(out, toTokenView(k))
	}
	c.JSON(http.StatusOK, out)
}

// GetToken godoc
// @Summary      Get child key
// @Description  Get a child key by id under the authenticated master
// @Tags         master
// @Produce      json
// @Security     MasterAuth
// @Param        id   path      int  true  "Token ID"
// @Success      200  {object}  TokenView
// @Failure      400  {object}  gin.H
// @Failure      401  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /v1/tokens/{id} [get]
func (h *MasterHandler) GetToken(c *gin.Context) {
	master, exists := c.Get("master")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"})
		return
	}
	m := master.(*model.Master)

	idRaw := strings.TrimSpace(c.Param("id"))
	idU64, err := strconv.ParseUint(idRaw, 10, 64)
	if err != nil || idU64 == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token id"})
		return
	}

	var k model.Key
	if err := h.db.Where("master_id = ? AND id = ?", m.ID, uint(idU64)).First(&k).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "token not found"})
		return
	}
	c.JSON(http.StatusOK, toTokenView(k))
}

type UpdateTokenRequest struct {
	Scopes             *string    `json:"scopes,omitempty"`
	Status             *string    `json:"status,omitempty"` // active/suspended
	ModelLimits        *string    `json:"model_limits,omitempty"`
	ModelLimitsEnabled *bool      `json:"model_limits_enabled,omitempty"`
	ExpiresAt          *time.Time `json:"expires_at,omitempty"`
	AllowIPs           *string    `json:"allow_ips,omitempty"`
	DenyIPs            *string    `json:"deny_ips,omitempty"`
}

// UpdateToken godoc
// @Summary      Update child key
// @Description  Update token scopes/status under the authenticated master
// @Tags         master
// @Accept       json
// @Produce      json
// @Security     MasterAuth
// @Param        id       path      int               true  "Token ID"
// @Param        request  body      UpdateTokenRequest true  "Update payload"
// @Success      200      {object}  TokenView
// @Failure      400      {object}  gin.H
// @Failure      401      {object}  gin.H
// @Failure      404      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /v1/tokens/{id} [put]
func (h *MasterHandler) UpdateToken(c *gin.Context) {
	master, exists := c.Get("master")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"})
		return
	}
	m := master.(*model.Master)

	idRaw := strings.TrimSpace(c.Param("id"))
	idU64, err := strconv.ParseUint(idRaw, 10, 64)
	if err != nil || idU64 == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token id"})
		return
	}

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

	update := make(map[string]any)
	if req.Scopes != nil {
		update["scopes"] = strings.TrimSpace(*req.Scopes)
	}
	if req.ModelLimits != nil {
		modelLimits := strings.TrimSpace(*req.ModelLimits)
		if modelLimits != "" {
			if err := h.masterService.ValidateModelLimits(m, modelLimits); err != nil {
				c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
				return
			}
		}
		update["model_limits"] = modelLimits
		if req.ModelLimitsEnabled == nil {
			update["model_limits_enabled"] = modelLimits != ""
		}
	}
	if req.ModelLimitsEnabled != nil {
		update["model_limits_enabled"] = *req.ModelLimitsEnabled
	}
	if req.ExpiresAt != nil {
		update["expires_at"] = req.ExpiresAt
	}
	if req.AllowIPs != nil {
		update["allow_ips"] = strings.TrimSpace(*req.AllowIPs)
	}
	if req.DenyIPs != nil {
		update["deny_ips"] = strings.TrimSpace(*req.DenyIPs)
	}
	if req.Status != nil {
		st := strings.ToLower(strings.TrimSpace(*req.Status))
		if st != "active" && st != "suspended" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "invalid status"})
			return
		}
		update["status"] = st
	}
	if len(update) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
		return
	}

	var k model.Key
	if err := h.db.Where("master_id = ? AND id = ?", m.ID, uint(idU64)).First(&k).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "token not found"})
		return
	}
	if err := h.db.Model(&k).Updates(update).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update token", "details": err.Error()})
		return
	}
	if err := h.db.Where("master_id = ? AND id = ?", m.ID, uint(idU64)).First(&k).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload token", "details": err.Error()})
		return
	}
	if err := h.syncService.SyncKey(&k); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync token", "details": err.Error()})
		return
	}
	c.JSON(http.StatusOK, toTokenView(k))
}

// DeleteToken godoc
// @Summary      Delete (revoke) child key
// @Description  Suspends a child key under the authenticated master
// @Tags         master
// @Produce      json
// @Security     MasterAuth
// @Param        id   path      int  true  "Token ID"
// @Success      200  {object}  gin.H
// @Failure      400  {object}  gin.H
// @Failure      401  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /v1/tokens/{id} [delete]
func (h *MasterHandler) DeleteToken(c *gin.Context) {
	master, exists := c.Get("master")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"})
		return
	}
	m := master.(*model.Master)

	idRaw := strings.TrimSpace(c.Param("id"))
	idU64, err := strconv.ParseUint(idRaw, 10, 64)
	if err != nil || idU64 == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token id"})
		return
	}

	var k model.Key
	if err := h.db.Where("master_id = ? AND id = ?", m.ID, uint(idU64)).First(&k).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "token not found"})
		return
	}

	if err := h.db.Model(&k).Update("status", "suspended").Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to revoke token", "details": err.Error()})
		return
	}
	if err := h.db.Where("master_id = ? AND id = ?", m.ID, uint(idU64)).First(&k).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload token", "details": err.Error()})
		return
	}
	if err := h.syncService.SyncKey(&k); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync token", "details": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"status": "revoked"})
}
