package api

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

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

type AccessResponse struct {
	DefaultNamespace string   `json:"default_namespace"`
	Namespaces       []string `json:"namespaces"`
}

type UpdateAccessRequest struct {
	DefaultNamespace string   `json:"default_namespace"`
	Namespaces       []string `json:"namespaces"`
	PropagateToKeys  bool     `json:"propagate_to_keys,omitempty"`
}

// GetMasterAccess godoc
// @Summary      Get master access settings
// @Description  Returns master default_namespace and namespaces
// @Tags         admin
// @Produce      json
// @Security     AdminAuth
// @Param        id   path      int  true  "Master ID"
// @Success      200  {object}  AccessResponse
// @Failure      400  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /admin/masters/{id}/access [get]
func (h *Handler) GetMasterAccess(c *gin.Context) {
	id, ok := parseUintParam(c, "id")
	if !ok {
		return
	}

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

	c.JSON(http.StatusOK, AccessResponse{
		DefaultNamespace: normalizeDefaultNamespace(m.DefaultNamespace),
		Namespaces:       normalizeNamespaces(m.Namespaces, m.DefaultNamespace),
	})
}

// UpdateMasterAccess godoc
// @Summary      Update master access settings
// @Description  Updates master default_namespace and namespaces; optionally propagate to existing keys
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     AdminAuth
// @Param        id       path      int                 true  "Master ID"
// @Param        request  body      UpdateAccessRequest  true  "Access settings"
// @Success      200      {object}  AccessResponse
// @Failure      400      {object}  gin.H
// @Failure      404      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /admin/masters/{id}/access [put]
func (h *Handler) UpdateMasterAccess(c *gin.Context) {
	id, ok := parseUintParam(c, "id")
	if !ok {
		return
	}

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

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

	nextDefault := strings.TrimSpace(req.DefaultNamespace)
	if nextDefault == "" {
		nextDefault = strings.TrimSpace(m.DefaultNamespace)
	}
	nextDefault = normalizeDefaultNamespace(nextDefault)

	nextNamespaces := m.Namespaces
	if req.Namespaces != nil {
		nextNamespaces = strings.Join(req.Namespaces, ",")
	}
	nsList := normalizeNamespaces(nextNamespaces, nextDefault)
	nextNamespaces = strings.Join(nsList, ",")
	if err := ensureNamespacesExist(h.db, nsList); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if err := h.db.Model(&m).Updates(map[string]any{
		"default_namespace": nextDefault,
		"namespaces":        nextNamespaces,
	}).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update master access", "details": err.Error()})
		return
	}

	if req.PropagateToKeys {
		if err := h.db.Model(&model.Key{}).Where("master_id = ?", m.ID).Updates(map[string]any{
			"default_namespace": nextDefault,
			"namespaces":        nextNamespaces,
		}).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to propagate access to keys", "details": err.Error()})
			return
		}

		var keys []model.Key
		if err := h.db.Where("master_id = ?", m.ID).Find(&keys).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload keys for sync", "details": err.Error()})
			return
		}
		for i := range keys {
			if err := h.sync.SyncKey(&keys[i]); err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync key access", "details": err.Error()})
				return
			}
		}
	}

	c.JSON(http.StatusOK, AccessResponse{
		DefaultNamespace: nextDefault,
		Namespaces:       nsList,
	})
}

// GetKeyAccess godoc
// @Summary      Get key access settings
// @Description  Returns key default_namespace and namespaces
// @Tags         admin
// @Produce      json
// @Security     AdminAuth
// @Param        id   path      int  true  "Key ID"
// @Success      200  {object}  AccessResponse
// @Failure      400  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /admin/keys/{id}/access [get]
func (h *Handler) GetKeyAccess(c *gin.Context) {
	id, ok := parseUintParam(c, "id")
	if !ok {
		return
	}
	var k model.Key
	if err := h.db.First(&k, id).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "key not found"})
		return
	}
	c.JSON(http.StatusOK, AccessResponse{
		DefaultNamespace: normalizeDefaultNamespace(k.DefaultNamespace),
		Namespaces:       normalizeNamespaces(k.Namespaces, k.DefaultNamespace),
	})
}

// UpdateKeyAccess godoc
// @Summary      Update key access settings
// @Description  Updates key default_namespace and namespaces and syncs to Redis
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     AdminAuth
// @Param        id       path      int                 true  "Key ID"
// @Param        request  body      UpdateAccessRequest  true  "Access settings"
// @Success      200      {object}  AccessResponse
// @Failure      400      {object}  gin.H
// @Failure      404      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /admin/keys/{id}/access [put]
func (h *Handler) UpdateKeyAccess(c *gin.Context) {
	id, ok := parseUintParam(c, "id")
	if !ok {
		return
	}

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

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

	nextDefault := strings.TrimSpace(req.DefaultNamespace)
	if nextDefault == "" {
		nextDefault = strings.TrimSpace(k.DefaultNamespace)
	}
	nextDefault = normalizeDefaultNamespace(nextDefault)

	nextNamespaces := k.Namespaces
	if req.Namespaces != nil {
		nextNamespaces = strings.Join(req.Namespaces, ",")
	}
	nsList := normalizeNamespaces(nextNamespaces, nextDefault)
	nextNamespaces = strings.Join(nsList, ",")
	if err := ensureNamespacesExist(h.db, nsList); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var master model.Master
	if err := h.db.First(&master, k.MasterID).Error; err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "master not found"})
		return
	}
	masterNamespaces := normalizeNamespaces(master.Namespaces, master.DefaultNamespace)
	if !isSubset(nsList, masterNamespaces) {
		c.JSON(http.StatusBadRequest, gin.H{"error": "namespaces must be a subset of master namespaces"})
		return
	}

	if err := h.db.Model(&k).Updates(map[string]any{
		"default_namespace": nextDefault,
		"namespaces":        nextNamespaces,
	}).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update key access", "details": err.Error()})
		return
	}
	if err := h.db.First(&k, id).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload key", "details": err.Error()})
		return
	}
	if err := h.sync.SyncKey(&k); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync key access", "details": err.Error()})
		return
	}
	c.JSON(http.StatusOK, AccessResponse{
		DefaultNamespace: nextDefault,
		Namespaces:       nsList,
	})
}

func normalizeDefaultNamespace(ns string) string {
	ns = strings.TrimSpace(ns)
	if ns == "" {
		return "default"
	}
	return ns
}

func normalizeNamespaces(raw string, defaultNamespace string) []string {
	defaultNamespace = normalizeDefaultNamespace(defaultNamespace)
	raw = strings.TrimSpace(raw)
	var parts []string
	if raw != "" {
		parts = strings.FieldsFunc(raw, func(r rune) bool {
			return r == ',' || r == ' ' || r == ';' || r == '\t' || r == '\n'
		})
	}
	out := make([]string, 0, len(parts)+1)
	seen := make(map[string]struct{}, len(parts)+1)
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		if _, ok := seen[p]; ok {
			continue
		}
		seen[p] = struct{}{}
		out = append(out, p)
	}
	if _, ok := seen[defaultNamespace]; !ok {
		out = append(out, defaultNamespace)
	}
	if len(out) == 0 {
		out = append(out, defaultNamespace)
	}
	return out
}

func ensureNamespacesExist(db *gorm.DB, namespaces []string) error {
	if db == nil {
		return fmt.Errorf("db required")
	}
	if len(namespaces) == 0 {
		return fmt.Errorf("namespaces required")
	}
	var rows []model.Namespace
	if err := db.Where("name IN ?", namespaces).Find(&rows).Error; err != nil {
		return fmt.Errorf("failed to load namespaces")
	}
	if len(rows) != len(namespaces) {
		return fmt.Errorf("namespace not found")
	}
	return nil
}

func isSubset(child, parent []string) bool {
	if len(child) == 0 {
		return true
	}
	parentSet := make(map[string]struct{}, len(parent))
	for _, p := range parent {
		parentSet[strings.TrimSpace(p)] = struct{}{}
	}
	for _, c := range child {
		if _, ok := parentSet[strings.TrimSpace(c)]; !ok {
			return false
		}
	}
	return true
}

func parseUintParam(c *gin.Context, name string) (uint, bool) {
	idRaw := strings.TrimSpace(c.Param(name))
	if idRaw == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": name + " required"})
		return 0, false
	}
	idU64, err := strconv.ParseUint(idRaw, 10, 64)
	if err != nil || idU64 == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid " + name})
		return 0, false
	}
	return uint(idU64), true
}
