package services

import (
	"errors"
	"gauth-central/internal/database"
	"gauth-central/internal/models"
	"log"
	"net/url"
	"strings"
	"time"

	"gorm.io/gorm"
)

type SettingsService struct {
	cacheService *CacheService
}

func NewSettingsService() *SettingsService {
	return &SettingsService{
		cacheService: NewCacheService(),
	}
}

// ==================== CORS WHITELIST ====================

func (s *SettingsService) GetAllCorsWhitelist() ([]models.CorsWhitelist, error) {
	var whitelists []models.CorsWhitelist
	err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&whitelists).Error
	return whitelists, err
}

func (s *SettingsService) GetActiveWhitelistOrigins() ([]string, error) {
	// Try cache first
	cached, err := s.cacheService.GetCorsWhitelist()
	if err == nil && cached != nil {
		return cached, nil
	}

	origins, err := s.getActiveWhitelistOriginsFromDB()
	if err != nil {
		return nil, err
	}

	// Cache for 1 hour
	s.cacheService.SetCorsWhitelist(origins, 1*time.Hour)

	return origins, nil
}

var ErrCorsOriginExists = errors.New("cors origin already exists")

func (s *SettingsService) CreateCorsWhitelist(whitelist *models.CorsWhitelist) error {
	var existing models.CorsWhitelist
	err := database.DB.Where("LOWER(origin) = LOWER(?)", whitelist.Origin).First(&existing).Error
	if err == nil {
		if existing.IsActive {
			return ErrCorsOriginExists
		}

		updates := map[string]interface{}{
			"is_active":   true,
			"description": whitelist.Description,
			"created_by":  whitelist.CreatedBy,
		}
		err = database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", existing.ID).Updates(updates).Error
		if err != nil {
			return err
		}
		s.InvalidateCorsCache()
		return nil
	}

	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}

	err = database.DB.Create(whitelist).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.InvalidateCorsCache()
	return nil
}

func (s *SettingsService) UpdateCorsWhitelist(id string, updates map[string]interface{}) error {
	err := database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", id).Updates(updates).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.InvalidateCorsCache()
	return nil
}

func (s *SettingsService) DeleteCorsWhitelist(id string) error {
	err := database.DB.Delete(&models.CorsWhitelist{}, "id = ?", id).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.InvalidateCorsCache()
	return nil
}

// ==================== CORS BLACKLIST ====================

func (s *SettingsService) GetAllCorsBlacklist() ([]models.CorsBlacklist, error) {
	var blacklists []models.CorsBlacklist
	err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&blacklists).Error
	return blacklists, err
}

func (s *SettingsService) GetActiveBlacklistOrigins() ([]string, error) {
	// Try cache first
	cached, err := s.cacheService.GetCorsBlacklist()
	if err == nil && cached != nil {
		return cached, nil
	}

	origins, err := s.getActiveBlacklistOriginsFromDB()
	if err != nil {
		return nil, err
	}

	// Cache for 1 hour
	s.cacheService.SetCorsBlacklist(origins, 1*time.Hour)

	return origins, nil
}

func (s *SettingsService) CreateCorsBlacklist(blacklist *models.CorsBlacklist) error {
	err := database.DB.Create(blacklist).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.InvalidateCorsCache()
	return nil
}

func (s *SettingsService) UpdateCorsBlacklist(id string, updates map[string]interface{}) error {
	err := database.DB.Model(&models.CorsBlacklist{}).Where("id = ?", id).Updates(updates).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.InvalidateCorsCache()
	return nil
}

func (s *SettingsService) DeleteCorsBlacklist(id string) error {
	err := database.DB.Delete(&models.CorsBlacklist{}, "id = ?", id).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.InvalidateCorsCache()
	return nil
}

// ==================== RATE LIMIT SETTINGS ====================

func (s *SettingsService) GetAllRateLimitSettings() ([]models.RateLimitSetting, error) {
	var settings []models.RateLimitSetting
	err := database.DB.Order("name ASC").Find(&settings).Error
	return settings, err
}

func (s *SettingsService) GetRateLimitSettingsMap() (map[string]*models.RateLimitSetting, error) {
	// Try cache first
	cached, err := s.cacheService.GetRateLimitSettings()
	if err == nil && cached != nil {
		return cached, nil
	}

	// Fetch from database
	var settings []models.RateLimitSetting
	err = database.DB.Where("is_active = ?", true).Find(&settings).Error
	if err != nil {
		return nil, err
	}

	settingsMap := make(map[string]*models.RateLimitSetting)
	for i := range settings {
		settingsMap[settings[i].Name] = &settings[i]
	}

	// Cache for 1 hour
	s.cacheService.SetRateLimitSettings(settingsMap, 1*time.Hour)

	return settingsMap, nil
}

func (s *SettingsService) GetRateLimitSettingByName(name string) (*models.RateLimitSetting, error) {
	settingsMap, err := s.GetRateLimitSettingsMap()
	if err != nil {
		return nil, err
	}

	setting, exists := settingsMap[name]
	if !exists {
		return nil, nil
	}

	return setting, nil
}

func (s *SettingsService) UpdateRateLimitSetting(id string, updates map[string]interface{}) error {
	err := database.DB.Model(&models.RateLimitSetting{}).Where("id = ?", id).Updates(updates).Error
	if err != nil {
		return err
	}

	// Invalidate cache
	s.cacheService.InvalidateRateLimitSettings()
	return nil
}

// Invalidate CORS caches (whitelist + blacklist)
func (s *SettingsService) InvalidateCorsCache() {
	s.cacheService.InvalidateCorsWhitelist()
	s.cacheService.InvalidateCorsBlacklist()
	log.Println("cors_cache_invalidated")
}

// Check if origin is allowed
func (s *SettingsService) IsOriginAllowed(origin string) (bool, error) {
	allowed, _, _, err := s.CheckOrigin(origin)
	return allowed, err
}

// CheckOrigin returns decision details for debug logging.
func (s *SettingsService) CheckOrigin(origin string) (bool, string, string, error) {
	// Check blacklist first
	blacklist, err := s.GetActiveBlacklistOrigins()
	if err != nil {
		return false, "", "", err
	}

	for _, blocked := range blacklist {
		if originMatchesEntry(origin, blocked) {
			return false, blocked, "blacklist", nil
		}
	}

	// Fallback: refresh blacklist on miss (stale cache protection)
	freshBlacklist, err := s.getActiveBlacklistOriginsFromDB()
	if err != nil {
		return false, "", "", err
	}
	if len(freshBlacklist) != 0 {
		s.cacheService.SetCorsBlacklist(freshBlacklist, 1*time.Hour)
	}
	for _, blocked := range freshBlacklist {
		if originMatchesEntry(origin, blocked) {
			return false, blocked, "blacklist", nil
		}
	}

	// Check whitelist
	whitelist, err := s.GetActiveWhitelistOrigins()
	if err != nil {
		return false, "", "", err
	}

	for _, allowed := range whitelist {
		if allowed == "*" || originMatchesEntry(origin, allowed) {
			return true, allowed, "whitelist", nil
		}
	}

	// Fallback: refresh whitelist on miss (stale cache protection)
	freshWhitelist, err := s.getActiveWhitelistOriginsFromDB()
	if err != nil {
		return false, "", "", err
	}
	if len(freshWhitelist) != 0 {
		s.cacheService.SetCorsWhitelist(freshWhitelist, 1*time.Hour)
	}
	for _, allowed := range freshWhitelist {
		if allowed == "*" || originMatchesEntry(origin, allowed) {
			return true, allowed, "whitelist", nil
		}
	}

	return false, "", "whitelist", nil
}

func (s *SettingsService) getActiveWhitelistOriginsFromDB() ([]string, error) {
	var whitelists []models.CorsWhitelist
	err := database.DB.Where("is_active = ?", true).Find(&whitelists).Error
	if err != nil {
		return nil, err
	}

	origins := make([]string, len(whitelists))
	for i, w := range whitelists {
		origins[i] = w.Origin
	}

	return origins, nil
}

func (s *SettingsService) getActiveBlacklistOriginsFromDB() ([]string, error) {
	var blacklists []models.CorsBlacklist
	err := database.DB.Where("is_active = ?", true).Find(&blacklists).Error
	if err != nil {
		return nil, err
	}

	origins := make([]string, len(blacklists))
	for i, b := range blacklists {
		origins[i] = b.Origin
	}

	return origins, nil
}

func originMatchesEntry(origin string, entry string) bool {
	origin = strings.TrimSpace(origin)
	entry = strings.TrimSpace(entry)
	if origin == "" || entry == "" {
		return false
	}

	originLower := strings.ToLower(origin)
	entryLower := strings.ToLower(entry)
	if strings.Contains(entryLower, "://") {
		return originLower == entryLower
	}

	parsed, err := url.Parse(originLower)
	if err != nil || parsed.Host == "" {
		return false
	}

	hostLower := strings.ToLower(parsed.Host)
	if entryLower == hostLower {
		return true
	}

	// Allow entries like "127.0.0.1" to match any port
	hostOnly := strings.Split(hostLower, ":")[0]
	return entryLower == hostOnly
}
