package handler

import (
	"strings"
	"time"

	"github.com/w3K-one/docker-ddns-server/dyndns/model"
	"github.com/labstack/gommon/log"
	"gorm.io/gorm"
)

const (
	MaxFailedAttempts = 3
	BlockDuration     = 168 * time.Hour // 7 days (1 week) in hours
	LookbackPeriod    = 72 * time.Hour  // Check failures in last 3 days
)

// LogFailedAuth records a failed authentication attempt
// WARNING: This logs passwords which is a security risk. Ensure database is properly secured.
// IMPORTANT: IP blocking only applies to admin panel attempts (/@/*), not API endpoints
func (h *Handler) LogFailedAuth(ipAddress, userAgent, path, username, password string) error {
	failedAuth := &model.FailedAuth{
		IPAddress: ipAddress,
		UserAgent: userAgent,
		Timestamp: time.Now(),
		Path:      path,
		Username:  username,
		Password:  password, // SECURITY WARNING: Storing attempted passwords
	}

	if err := h.DB.Create(failedAuth).Error; err != nil {
		log.Error("Failed to log authentication failure:", err)
		return err
	}

	// CRITICAL: Only check for IP blocking if this was an admin panel attempt
	// API endpoints (like /nic/update, /update, /v2/update, /v3/update) should NOT trigger blocking
	if strings.HasPrefix(path, "/@/") {
		log.Infof("Admin panel failed auth from %s - checking for IP block", ipAddress)
		go h.CheckAndBlockIP(ipAddress)
	} else {
		log.Infof("API endpoint failed auth from %s on %s - NOT checking for IP block", ipAddress, path)
	}

	return nil
}

// CheckAndBlockIP checks if an IP has exceeded failed attempts and blocks it
// ONLY COUNTS FAILURES TO ADMIN PANEL (/@/*), NOT API ENDPOINTS
func (h *Handler) CheckAndBlockIP(ipAddress string) error {
	// Count failed attempts to ADMIN PANEL ONLY in the lookback period
	var count int64
	lookbackTime := time.Now().Add(-LookbackPeriod)

	err := h.DB.Model(&model.FailedAuth{}).
		Where("ip_address = ? AND timestamp > ? AND path LIKE '/@/%'", ipAddress, lookbackTime).
		Count(&count).Error

	if err != nil {
		log.Error("Failed to count authentication failures:", err)
		return err
	}

	log.Infof("IP %s has %d failed ADMIN PANEL attempts in last %v", ipAddress, count, LookbackPeriod)

	// If exceeded threshold, block the IP
	if count >= MaxFailedAttempts {
		return h.BlockIP(ipAddress, int(count), "Exceeded maximum failed admin authentication attempts")
	}

	return nil
}

// BlockIP adds an IP to the blocked list
func (h *Handler) BlockIP(ipAddress string, failureCount int, reason string) error {
	// Check if IP is already blocked
	var existingBlock model.BlockedIP
	err := h.DB.Where("ip_address = ?", ipAddress).First(&existingBlock).Error

	if err == nil {
		// Update existing block
		existingBlock.FailureCount = failureCount
		existingBlock.LastAttemptAt = time.Now()
		existingBlock.BlockedUntil = time.Now().Add(BlockDuration)
		existingBlock.Reason = reason

		if err := h.DB.Save(&existingBlock).Error; err != nil {
			log.Error("Failed to update blocked IP:", err)
			return err
		}

		log.Warnf("Updated block for IP %s (failures: %d)", ipAddress, failureCount)
		return nil
	}

	// Create new block
	blockedIP := &model.BlockedIP{
		IPAddress:     ipAddress,
		BlockedAt:     time.Now(),
		BlockedUntil:  time.Now().Add(BlockDuration),
		FailureCount:  failureCount,
		IsPermanent:   false,
		LastAttemptAt: time.Now(),
		Reason:        reason,
	}

	if err := h.DB.Create(blockedIP).Error; err != nil {
		log.Error("Failed to block IP:", err)
		return err
	}

	log.Warnf("Blocked IP %s for %v (failures: %d, reason: %s)", 
		ipAddress, BlockDuration, failureCount, reason)

	return nil
}

// IsIPBlocked checks if an IP address is currently blocked
func (h *Handler) IsIPBlocked(ipAddress string) (bool, *model.BlockedIP, error) {
	var blockedIP model.BlockedIP
	err := h.DB.Where("ip_address = ?", ipAddress).First(&blockedIP).Error

	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return false, nil, nil
		}
		return false, nil, err
	}

	// Check if block is still active
	if blockedIP.IsBlocked() {
		return true, &blockedIP, nil
	}

	return false, nil, nil
}

// UnblockIP removes an IP from the blocked list
func (h *Handler) UnblockIP(ipAddress string) error {
	result := h.DB.Where("ip_address = ?", ipAddress).Delete(&model.BlockedIP{})
	if result.Error != nil {
		log.Error("Failed to unblock IP:", result.Error)
		return result.Error
	}

	log.Infof("Unblocked IP %s", ipAddress)
	return nil
}

// GetClientIP extracts the real client IP from the request
func GetClientIP(r interface{}) string {
	// This function can be enhanced to check X-Forwarded-For, X-Real-IP headers
	// For now, we'll use a simple extraction
	
	// You'll need to pass the Echo context here
	// This is a helper that should be called from middleware
	return ""
}

// CleanupExpiredBlocks removes expired blocks from the database
func (h *Handler) CleanupExpiredBlocks() error {
	result := h.DB.Where("is_permanent = ? AND blocked_until < ?", false, time.Now()).
		Delete(&model.BlockedIP{})
	
	if result.Error != nil {
		log.Error("Failed to cleanup expired blocks:", result.Error)
		return result.Error
	}

	if result.RowsAffected > 0 {
		log.Infof("Cleaned up %d expired IP blocks", result.RowsAffected)
	}

	return nil
}

// CleanupOldFailedAuths removes old failed authentication records
func (h *Handler) CleanupOldFailedAuths() error {
	// Keep records for 30 days
	cutoffTime := time.Now().Add(-30 * 24 * time.Hour)
	
	result := h.DB.Where("timestamp < ?", cutoffTime).Delete(&model.FailedAuth{})
	
	if result.Error != nil {
		log.Error("Failed to cleanup old failed auths:", result.Error)
		return result.Error
	}

	if result.RowsAffected > 0 {
		log.Infof("Cleaned up %d old failed authentication records", result.RowsAffected)
	}

	return nil
}

// GetBlockedIPs returns all currently blocked IPs
func (h *Handler) GetBlockedIPs() ([]model.BlockedIP, error) {
	var blockedIPs []model.BlockedIP
	err := h.DB.Order("blocked_at DESC").Find(&blockedIPs).Error
	return blockedIPs, err
}

// GetRecentFailedAuths returns recent failed authentication attempts
func (h *Handler) GetRecentFailedAuths(limit int) ([]model.FailedAuth, error) {
	var failedAuths []model.FailedAuth
	err := h.DB.Order("timestamp DESC").Limit(limit).Find(&failedAuths).Error
	return failedAuths, err
}
