package com.gnx.telemedicine.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Slf4j
public class RateLimitingService {

    @Value("${rate-limit.requests-per-minute:60}")
    private int requestsPerMinute = 60;

    @Value("${rate-limit.requests-per-hour:1000}")
    private int requestsPerHour = 1000;

    @Value("${rate-limit.login-attempts-per-minute:5}")
    private int loginAttemptsPerMinute = 5;

    // Store request counts: key = "userIdentifier:endpoint", value = RequestWindow
    private final Map<String, RequestWindow> requestCounts = new ConcurrentHashMap<>();

    public boolean isAllowed(String userIdentifier, String endpoint, String method) {
        return checkRateLimit(userIdentifier, endpoint, method).isAllowed();
    }

    public RateLimitResult checkRateLimit(String userIdentifier, String endpoint, String method) {
        String key = userIdentifier + ":" + endpoint + ":" + method;
        Instant now = Instant.now();
        
        // Special rate limiting for login endpoint
        if (endpoint.contains("/auth/login") || endpoint.contains("/login")) {
            return checkLoginRateLimit(key, now);
        }
        
        return checkGeneralRateLimit(key, now);
    }

    private RateLimitResult checkLoginRateLimit(String key, Instant now) {
        RequestWindow window = requestCounts.computeIfAbsent(key, k -> new RequestWindow());
        
        int currentCount = window.getMinuteCount(now);
        int limit = loginAttemptsPerMinute;
        int remaining = Math.max(0, limit - currentCount);
        long resetTime = ((now.getEpochSecond() / 60) + 1) * 60; // Next minute
        
        // Check per-minute limit for login
        if (currentCount >= limit) {
            log.warn("Rate limit exceeded for login: {}", key);
            return new RateLimitResult(false, limit, 0, resetTime);
        }
        
        window.recordRequest(now);
        return new RateLimitResult(true, limit, remaining - 1, resetTime);
    }

    private RateLimitResult checkGeneralRateLimit(String key, Instant now) {
        RequestWindow window = requestCounts.computeIfAbsent(key, k -> new RequestWindow());
        
        int minuteCount = window.getMinuteCount(now);
        int hourCount = window.getHourCount(now);
        
        // Determine which limit applies (the stricter one)
        int limit;
        int currentCount;
        long resetTime;
        
        if (minuteCount >= requestsPerMinute) {
            limit = requestsPerMinute;
            currentCount = minuteCount;
            resetTime = ((now.getEpochSecond() / 60) + 1) * 60; // Next minute
        } else if (hourCount >= requestsPerHour) {
            limit = requestsPerHour;
            currentCount = hourCount;
            resetTime = ((now.getEpochSecond() / 3600) + 1) * 3600; // Next hour
        } else {
            // Use per-minute limit as primary
            limit = requestsPerMinute;
            currentCount = minuteCount;
            resetTime = ((now.getEpochSecond() / 60) + 1) * 60; // Next minute
        }
        
        int remaining = Math.max(0, limit - currentCount);
        
        // Check per-minute limit
        if (minuteCount >= requestsPerMinute) {
            log.warn("Rate limit exceeded (per minute): {}", key);
            return new RateLimitResult(false, requestsPerMinute, 0, resetTime);
        }
        
        // Check per-hour limit
        if (hourCount >= requestsPerHour) {
            log.warn("Rate limit exceeded (per hour): {}", key);
            resetTime = ((now.getEpochSecond() / 3600) + 1) * 3600;
            return new RateLimitResult(false, requestsPerHour, 0, resetTime);
        }
        
        window.recordRequest(now);
        // Recalculate remaining after recording
        remaining = Math.max(0, limit - currentCount - 1);
        return new RateLimitResult(true, limit, remaining, resetTime);
    }

    public void clearRateLimit(String userIdentifier, String endpoint) {
        String key = userIdentifier + ":" + endpoint;
        requestCounts.remove(key);
    }

    // Inner class to track request windows
    private static class RequestWindow {
        private long currentMinuteWindow;
        private long currentHourWindow;
        private int minuteCount;
        private int hourCount;
        
        public RequestWindow() {
            Instant now = Instant.now();
            currentMinuteWindow = now.getEpochSecond() / 60;
            currentHourWindow = now.getEpochSecond() / 3600;
            minuteCount = 0;
            hourCount = 0;
        }
        
        public void recordRequest(Instant now) {
            long minuteWindow = now.getEpochSecond() / 60;
            long hourWindow = now.getEpochSecond() / 3600;
            
            // Reset minute count if window changed
            if (minuteWindow != currentMinuteWindow) {
                currentMinuteWindow = minuteWindow;
                minuteCount = 0;
            }
            
            // Reset hour count if window changed
            if (hourWindow != currentHourWindow) {
                currentHourWindow = hourWindow;
                hourCount = 0;
            }
            
            minuteCount++;
            hourCount++;
        }
        
        public int getMinuteCount(Instant now) {
            long minuteWindow = now.getEpochSecond() / 60;
            if (minuteWindow != currentMinuteWindow) {
                return 0; // New window, count is 0
            }
            return minuteCount;
        }
        
        public int getHourCount(Instant now) {
            long hourWindow = now.getEpochSecond() / 3600;
            if (hourWindow != currentHourWindow) {
                return 0; // New window, count is 0
            }
            return hourCount;
        }
    }
    
    /**
     * Result of rate limit check.
     */
    public static class RateLimitResult {
        private final boolean allowed;
        private final int limit;
        private final int remaining;
        private final long resetTime;
        
        public RateLimitResult(boolean allowed, int limit, int remaining, long resetTime) {
            this.allowed = allowed;
            this.limit = limit;
            this.remaining = remaining;
            this.resetTime = resetTime;
        }
        
        public boolean isAllowed() {
            return allowed;
        }
        
        public int getLimit() {
            return limit;
        }
        
        public int getRemaining() {
            return remaining;
        }
        
        public long getResetTime() {
            return resetTime;
        }
    }
}

