package com.saas.shared.security;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;

/**
 * Login Attempt Service
 * 
 * Tracks failed login attempts and blocks IP addresses after threshold is
 * exceeded.
 * Uses Redis for distributed caching across multiple application instances.
 * 
 * Protection policy:
 * - Block after 5 failed attempts
 * - Block duration: 15 minutes
 * - Counter resets after successful login
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class LoginAttemptService {

    private final RedisTemplate<String, Object> redisTemplate;

    private static final int MAX_ATTEMPTS = 5;
    private static final Duration BLOCK_DURATION = Duration.ofMinutes(15);
    private static final String ATTEMPTS_KEY_PREFIX = "login:attempts:";
    private static final String BLOCK_KEY_PREFIX = "login:blocked:";

    /**
     * Record a failed login attempt for an IP address
     * 
     * @param ipAddress Client IP address
     */
    public void loginFailed(String ipAddress) {
        String attemptsKey = ATTEMPTS_KEY_PREFIX + ipAddress;

        // Increment attempt counter
        Integer attempts = (Integer) redisTemplate.opsForValue().get(attemptsKey);
        if (attempts == null) {
            attempts = 0;
        }
        attempts++;

        // Store with 15 minute expiry
        redisTemplate.opsForValue().set(attemptsKey, attempts, BLOCK_DURATION);

        log.warn("🔐 Failed login attempt #{} from IP: {}", attempts, ipAddress);

        // Block IP if threshold exceeded
        if (attempts >= MAX_ATTEMPTS) {
            blockIp(ipAddress);
            log.error("🚫 IP BLOCKED due to {} failed login attempts: {}", attempts, ipAddress);
        }
    }

    /**
     * Record a successful login (resets attempt counter)
     * 
     * @param ipAddress Client IP address
     */
    public void loginSucceeded(String ipAddress) {
        String attemptsKey = ATTEMPTS_KEY_PREFIX + ipAddress;
        redisTemplate.delete(attemptsKey);
        log.debug("✅ Login successful, counter reset for IP: {}", ipAddress);
    }

    /**
     * Check if an IP address is currently blocked
     * 
     * @param ipAddress Client IP address
     * @return true if blocked, false otherwise
     */
    public boolean isBlocked(String ipAddress) {
        String blockKey = BLOCK_KEY_PREFIX + ipAddress;
        Boolean blocked = redisTemplate.hasKey(blockKey);
        return blocked != null && blocked;
    }

    /**
     * Get remaining failed attempts before block
     * 
     * @param ipAddress Client IP address
     * @return Number of attempts remaining (5 = no attempts yet, 0 = blocked)
     */
    public int getRemainingAttempts(String ipAddress) {
        if (isBlocked(ipAddress)) {
            return 0;
        }

        String attemptsKey = ATTEMPTS_KEY_PREFIX + ipAddress;
        Integer attempts = (Integer) redisTemplate.opsForValue().get(attemptsKey);
        if (attempts == null) {
            return MAX_ATTEMPTS;
        }
        return Math.max(0, MAX_ATTEMPTS - attempts);
    }

    /**
     * Block an IP address for BLOCK_DURATION
     * 
     * @param ipAddress IP address to block
     */
    private void blockIp(String ipAddress) {
        String blockKey = BLOCK_KEY_PREFIX + ipAddress;
        redisTemplate.opsForValue().set(blockKey, 1, BLOCK_DURATION);
    }

    /**
     * Manually unblock an IP address (admin override)
     * 
     * @param ipAddress IP address to unblock
     */
    public void unblockIp(String ipAddress) {
        String attemptsKey = ATTEMPTS_KEY_PREFIX + ipAddress;
        String blockKey = BLOCK_KEY_PREFIX + ipAddress;

        redisTemplate.delete(attemptsKey);
        redisTemplate.delete(blockKey);

        log.info("🔓 IP manually unblocked (admin override): {}", ipAddress);
    }

    /**
     * Get time remaining until IP is unblocked (in seconds)
     * 
     * @param ipAddress Client IP address
     * @return Seconds until unblock, or 0 if not blocked
     */
    public long getBlockTimeRemaining(String ipAddress) {
        if (!isBlocked(ipAddress)) {
            return 0;
        }

        String blockKey = BLOCK_KEY_PREFIX + ipAddress;
        Long ttl = redisTemplate.getExpire(blockKey);
        return ttl != null ? ttl : 0;
    }
}
