package controllers

import (
	configs "ares/config"
	database "ares/database/config"
	"ares/database/models"
	"ares/middlewares"
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"

	"github.com/gofiber/fiber/v3"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
)

const (
	corsWhitelistCacheKey = "admin:cors:whitelist:list"
	corsBlacklistCacheKey = "admin:cors:blacklist:list"
	rateLimitCacheKey     = "admin:rate_limit:list"
	securityCacheTTL      = 60
)

type CorsWhitelistRequest struct {
	Origin      string `json:"origin" validate:"required"`
	Description string `json:"description"`
	IsActive    *bool  `json:"is_active"`
}

type CorsBlacklistRequest struct {
	Origin   string `json:"origin" validate:"required"`
	Reason   string `json:"reason"`
	IsActive *bool  `json:"is_active"`
}

type RateLimitSettingRequest struct {
	Name          string `json:"name" validate:"required"`
	Description   string `json:"description"`
	MaxRequests   int64  `json:"max_requests" validate:"required,min=1"`
	WindowSeconds int    `json:"window_seconds" validate:"required,min=1"`
	IsActive      *bool  `json:"is_active"`
}

func ListCorsWhitelists(c fiber.Ctx) error {
	if database.DB == nil {
		return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{"error": "database is not configured"})
	}

	var items []models.CorsWhitelist
	if cached, err := database.Get(corsWhitelistCacheKey); err == nil {
		if unmarshalErr := json.Unmarshal([]byte(cached), &items); unmarshalErr == nil {
			securityLogf("[security][cors-whitelist][cache-hit] count=%d", len(items))
			return c.JSON(fiber.Map{"count": len(items), "items": items})
		}
	} else if !errors.Is(err, redis.Nil) {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "cache read error"})
	}

	if err := database.DB.Order("id DESC").Find(&items).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
	}

	cacheJSON, _ := json.Marshal(items)
	_ = database.SetEx(corsWhitelistCacheKey, string(cacheJSON), securityCacheTTL)
	securityLogf("[security][cors-whitelist][db-load] count=%d", len(items))

	return c.JSON(fiber.Map{"count": len(items), "items": items})
}

func CreateCorsWhitelist(c fiber.Ctx) error {
	var req CorsWhitelistRequest
	if err := c.Bind().JSON(&req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}
	if err := validate.Struct(req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	item := models.CorsWhitelist{
		Origin:      strings.TrimSpace(req.Origin),
		Description: strings.TrimSpace(req.Description),
		IsActive:    boolValue(req.IsActive, true),
		CreatedBy:   currentActor(c),
	}
	if err := database.DB.Create(&item).Error; err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "record could not be created"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-whitelist][create] origin=%s by=%s", item.Origin, item.CreatedBy)
	return c.Status(http.StatusCreated).JSON(fiber.Map{"item": item})
}

func UpdateCorsWhitelist(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	var req CorsWhitelistRequest
	if err := c.Bind().JSON(&req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}
	if err := validate.Struct(req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	var item models.CorsWhitelist
	if err := database.DB.First(&item, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
		}
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
	}

	item.Origin = strings.TrimSpace(req.Origin)
	item.Description = strings.TrimSpace(req.Description)
	item.IsActive = boolValue(req.IsActive, item.IsActive)
	item.CreatedBy = currentActor(c)
	if err := database.DB.Save(&item).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be updated"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-whitelist][update] id=%d origin=%s by=%s", item.ID, item.Origin, item.CreatedBy)
	return c.JSON(fiber.Map{"item": item})
}

func DeleteCorsWhitelist(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	if err := database.DB.Delete(&models.CorsWhitelist{}, id).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be deleted"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-whitelist][soft-delete] id=%d by=%s", id, currentActor(c))
	return c.JSON(fiber.Map{"message": "soft deleted", "id": id})
}

func HardDeleteCorsWhitelist(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	if err := database.DB.Unscoped().Delete(&models.CorsWhitelist{}, id).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be hard-deleted"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-whitelist][hard-delete] id=%d by=%s", id, currentActor(c))
	return c.JSON(fiber.Map{"message": "hard deleted", "id": id})
}

func ListCorsBlacklists(c fiber.Ctx) error {
	if database.DB == nil {
		return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{"error": "database is not configured"})
	}

	var items []models.CorsBlacklist
	if cached, err := database.Get(corsBlacklistCacheKey); err == nil {
		if unmarshalErr := json.Unmarshal([]byte(cached), &items); unmarshalErr == nil {
			securityLogf("[security][cors-blacklist][cache-hit] count=%d", len(items))
			return c.JSON(fiber.Map{"count": len(items), "items": items})
		}
	} else if !errors.Is(err, redis.Nil) {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "cache read error"})
	}

	if err := database.DB.Order("id DESC").Find(&items).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
	}

	cacheJSON, _ := json.Marshal(items)
	_ = database.SetEx(corsBlacklistCacheKey, string(cacheJSON), securityCacheTTL)
	securityLogf("[security][cors-blacklist][db-load] count=%d", len(items))

	return c.JSON(fiber.Map{"count": len(items), "items": items})
}

func CreateCorsBlacklist(c fiber.Ctx) error {
	var req CorsBlacklistRequest
	if err := c.Bind().JSON(&req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}
	if err := validate.Struct(req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	item := models.CorsBlacklist{
		Origin:    strings.TrimSpace(req.Origin),
		Reason:    strings.TrimSpace(req.Reason),
		IsActive:  boolValue(req.IsActive, true),
		CreatedBy: currentActor(c),
	}
	if err := database.DB.Create(&item).Error; err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "record could not be created"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-blacklist][create] origin=%s by=%s", item.Origin, item.CreatedBy)
	return c.Status(http.StatusCreated).JSON(fiber.Map{"item": item})
}

func UpdateCorsBlacklist(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	var req CorsBlacklistRequest
	if err := c.Bind().JSON(&req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}
	if err := validate.Struct(req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	var item models.CorsBlacklist
	if err := database.DB.First(&item, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
		}
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
	}

	item.Origin = strings.TrimSpace(req.Origin)
	item.Reason = strings.TrimSpace(req.Reason)
	item.IsActive = boolValue(req.IsActive, item.IsActive)
	item.CreatedBy = currentActor(c)
	if err := database.DB.Save(&item).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be updated"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-blacklist][update] id=%d origin=%s by=%s", item.ID, item.Origin, item.CreatedBy)
	return c.JSON(fiber.Map{"item": item})
}

func DeleteCorsBlacklist(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	if err := database.DB.Delete(&models.CorsBlacklist{}, id).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be deleted"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-blacklist][soft-delete] id=%d by=%s", id, currentActor(c))
	return c.JSON(fiber.Map{"message": "soft deleted", "id": id})
}

func HardDeleteCorsBlacklist(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	if err := database.DB.Unscoped().Delete(&models.CorsBlacklist{}, id).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be hard-deleted"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][cors-blacklist][hard-delete] id=%d by=%s", id, currentActor(c))
	return c.JSON(fiber.Map{"message": "hard deleted", "id": id})
}

func ListRateLimitSettings(c fiber.Ctx) error {
	if database.DB == nil {
		return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{"error": "database is not configured"})
	}

	var items []models.RateLimitSetting
	if cached, err := database.Get(rateLimitCacheKey); err == nil {
		if unmarshalErr := json.Unmarshal([]byte(cached), &items); unmarshalErr == nil {
			securityLogf("[security][rate-limit][cache-hit] count=%d", len(items))
			return c.JSON(fiber.Map{"count": len(items), "items": items})
		}
	} else if !errors.Is(err, redis.Nil) {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "cache read error"})
	}

	if err := database.DB.Order("id DESC").Find(&items).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
	}

	cacheJSON, _ := json.Marshal(items)
	_ = database.SetEx(rateLimitCacheKey, string(cacheJSON), securityCacheTTL)
	securityLogf("[security][rate-limit][db-load] count=%d", len(items))

	return c.JSON(fiber.Map{"count": len(items), "items": items})
}

func CreateRateLimitSetting(c fiber.Ctx) error {
	var req RateLimitSettingRequest
	if err := c.Bind().JSON(&req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}
	if err := validate.Struct(req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	item := models.RateLimitSetting{
		Name:          strings.TrimSpace(req.Name),
		Description:   strings.TrimSpace(req.Description),
		MaxRequests:   req.MaxRequests,
		WindowSeconds: req.WindowSeconds,
		IsActive:      boolValue(req.IsActive, true),
		UpdatedBy:     currentActor(c),
	}
	if err := database.DB.Create(&item).Error; err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "record could not be created"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][rate-limit][create] name=%s max=%d window=%ds by=%s", item.Name, item.MaxRequests, item.WindowSeconds, item.UpdatedBy)
	return c.Status(http.StatusCreated).JSON(fiber.Map{"item": item})
}

func UpdateRateLimitSetting(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	var req RateLimitSettingRequest
	if err := c.Bind().JSON(&req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}
	if err := validate.Struct(req); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
	}

	var item models.RateLimitSetting
	if err := database.DB.First(&item, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "record not found"})
		}
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "database error"})
	}

	item.Name = strings.TrimSpace(req.Name)
	item.Description = strings.TrimSpace(req.Description)
	item.MaxRequests = req.MaxRequests
	item.WindowSeconds = req.WindowSeconds
	item.IsActive = boolValue(req.IsActive, item.IsActive)
	item.UpdatedBy = currentActor(c)
	if err := database.DB.Save(&item).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be updated"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][rate-limit][update] id=%d name=%s max=%d window=%ds by=%s", item.ID, item.Name, item.MaxRequests, item.WindowSeconds, item.UpdatedBy)
	return c.JSON(fiber.Map{"item": item})
}

func DeleteRateLimitSetting(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	if err := database.DB.Delete(&models.RateLimitSetting{}, id).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be deleted"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][rate-limit][soft-delete] id=%d by=%s", id, currentActor(c))
	return c.JSON(fiber.Map{"message": "soft deleted", "id": id})
}

func HardDeleteRateLimitSetting(c fiber.Ctx) error {
	id, err := parseID(c.Params("id"))
	if err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "invalid id"})
	}

	if err := database.DB.Unscoped().Delete(&models.RateLimitSetting{}, id).Error; err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "record could not be hard-deleted"})
	}
	invalidateSecurityCaches()
	securityLogf("[security][rate-limit][hard-delete] id=%d by=%s", id, currentActor(c))
	return c.JSON(fiber.Map{"message": "hard deleted", "id": id})
}

func parseID(param string) (uint, error) {
	v, err := strconv.ParseUint(strings.TrimSpace(param), 10, 64)
	if err != nil || v == 0 {
		return 0, errors.New("invalid id")
	}
	return uint(v), nil
}

func invalidateSecurityCaches() {
	_ = database.Delete(corsWhitelistCacheKey)
	_ = database.Delete(corsBlacklistCacheKey)
	_ = database.Delete(rateLimitCacheKey)
	_ = database.Delete("cors:active:whitelist")
	_ = database.Delete("cors:active:blacklist")
}

func currentActor(c fiber.Ctx) string {
	if claims, ok := middlewares.GetAuthClaims(c); ok && strings.TrimSpace(claims.Email) != "" {
		return claims.Email
	}
	return "system"
}

func boolValue(v *bool, fallback bool) bool {
	if v == nil {
		return fallback
	}
	return *v
}

func securityLogf(format string, args ...interface{}) {
	if configs.AppConfig != nil && configs.AppConfig.CorsDebug {
		if configs.Logger != nil {
			configs.Logger.Sugar().Infof(format, args...)
		}
	}
}
