package api

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

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
)

const featuresKey = "meta:features"
const (
	logRetentionFeatureKey   = "log_retention_days"
	logMaxRecordsFeatureKey  = "log_max_records"
	logRequestBodyFeatureKey = "log_request_body_enabled"
	logRetentionRedisKey     = "meta:log:retention_days"
	logMaxRecordsRedisKey    = "meta:log:max_records"
)

// FeatureHandler manages lightweight feature flags stored in Redis for DP/CP runtime toggles.
// Values are stored as plain strings in a Redis hash.
type FeatureHandler struct {
	rdb *redis.Client
}

func NewFeatureHandler(rdb *redis.Client) *FeatureHandler {
	return &FeatureHandler{rdb: rdb}
}

// ListFeatures godoc
// @Summary      List feature flags
// @Description  Returns all feature flags stored in Redis (meta:features)
// @Tags         admin
// @Produce      json
// @Security     AdminAuth
// @Success      200      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /admin/features [get]
func (h *FeatureHandler) ListFeatures(c *gin.Context) {
	if h.rdb == nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "redis not configured"})
		return
	}
	m, err := h.rdb.HGetAll(c.Request.Context(), featuresKey).Result()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read features", "details": err.Error()})
		return
	}
	if v, err := h.rdb.Get(c.Request.Context(), logRetentionRedisKey).Result(); err == nil {
		m[logRetentionFeatureKey] = strings.TrimSpace(v)
	} else if err != redis.Nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read log retention", "details": err.Error()})
		return
	}
	if v, err := h.rdb.Get(c.Request.Context(), logMaxRecordsRedisKey).Result(); err == nil {
		m[logMaxRecordsFeatureKey] = strings.TrimSpace(v)
	} else if err != redis.Nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read log max records", "details": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"features": m})
}

type UpdateFeaturesRequest map[string]any

// UpdateFeatures godoc
// @Summary      Update feature flags
// @Description  Updates selected feature flags (meta:features). Values are stored as strings.
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     AdminAuth
// @Param        request  body      object  true  "Feature map"
// @Success      200      {object}  gin.H
// @Failure      400      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /admin/features [put]
func (h *FeatureHandler) UpdateFeatures(c *gin.Context) {
	if h.rdb == nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "redis not configured"})
		return
	}

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

	updates := make(map[string]string, len(req))
	specialUpdates := make(map[string]string, 2)
	for k, v := range req {
		key := strings.TrimSpace(k)
		if key == "" {
			continue
		}
		switch key {
		case logRetentionFeatureKey:
			value, err := updateLogOverride(c.Request.Context(), h.rdb, logRetentionRedisKey, v)
			if err != nil {
				c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
				return
			}
			specialUpdates[key] = value
			continue
		case logMaxRecordsFeatureKey:
			value, err := updateLogOverride(c.Request.Context(), h.rdb, logMaxRecordsRedisKey, v)
			if err != nil {
				c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
				return
			}
			specialUpdates[key] = value
			continue
		}
		switch vv := v.(type) {
		case string:
			updates[key] = strings.TrimSpace(vv)
		case bool:
			if vv {
				updates[key] = "true"
			} else {
				updates[key] = "false"
			}
		case float64:
			// JSON numbers decode as float64; keep as integer-looking string when possible.
			updates[key] = fmt.Sprintf("%v", vv)
		default:
			updates[key] = fmt.Sprintf("%v", vv)
		}
	}

	if len(updates) == 0 && len(specialUpdates) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no valid feature updates"})
		return
	}

	if len(updates) > 0 {
		if err := h.rdb.HSet(c.Request.Context(), featuresKey, updates).Err(); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update features", "details": err.Error()})
			return
		}
	}

	for k, v := range specialUpdates {
		updates[k] = v
	}
	c.JSON(http.StatusOK, gin.H{"updated": updates})
}

func updateLogOverride(ctx context.Context, rdb *redis.Client, key string, value any) (string, error) {
	n, err := parsePositiveInt64(value)
	if err != nil {
		return "", err
	}
	if n <= 0 {
		if err := rdb.Del(ctx, key).Err(); err != nil && err != redis.Nil {
			return "", fmt.Errorf("failed to clear %s: %w", key, err)
		}
		return "0", nil
	}
	raw := strconv.FormatInt(n, 10)
	if err := rdb.Set(ctx, key, raw, 0).Err(); err != nil {
		return "", fmt.Errorf("failed to update %s: %w", key, err)
	}
	return raw, nil
}

func parsePositiveInt64(value any) (int64, error) {
	switch v := value.(type) {
	case nil:
		return 0, nil
	case float64:
		if v < 0 {
			return 0, fmt.Errorf("value must be >= 0")
		}
		if v != math.Trunc(v) {
			return 0, fmt.Errorf("value must be an integer")
		}
		return int64(v), nil
	case string:
		trimmed := strings.TrimSpace(v)
		if trimmed == "" {
			return 0, nil
		}
		n, err := strconv.ParseInt(trimmed, 10, 64)
		if err != nil {
			return 0, fmt.Errorf("value must be an integer")
		}
		if n < 0 {
			return 0, fmt.Errorf("value must be >= 0")
		}
		return n, nil
	default:
		return 0, fmt.Errorf("value must be an integer")
	}
}
