//
//  RateLimiter.swift
//  SportsTime
//
//  Rate limiting for API providers to respect their rate limits.
//

import Foundation

// MARK: - Rate Limiter

/// Per-provider rate limiting to avoid hitting API limits
actor RateLimiter {

    // MARK: - Types

    struct ProviderConfig {
        let name: String
        let minInterval: TimeInterval  // Minimum time between requests
        let burstLimit: Int           // Max requests in burst window
        let burstWindow: TimeInterval // Window for burst counting
    }

    // MARK: - Properties

    private var lastRequestTimes: [String: Date] = [:]
    private var requestCounts: [String: [Date]] = [:]
    private var configs: [String: ProviderConfig] = [:]

    // MARK: - Default Configurations

    /// Default provider rate limit configurations
    private static let defaultConfigs: [ProviderConfig] = [
        ProviderConfig(name: "mlb_stats", minInterval: 0.1, burstLimit: 30, burstWindow: 60),      // 10 req/sec
        ProviderConfig(name: "nhl_stats", minInterval: 0.2, burstLimit: 20, burstWindow: 60),      // 5 req/sec
        ProviderConfig(name: "nba_stats", minInterval: 0.5, burstLimit: 10, burstWindow: 60),      // 2 req/sec
        ProviderConfig(name: "espn", minInterval: 1.0, burstLimit: 30, burstWindow: 60),            // 1 req/sec
        ProviderConfig(name: "sports_reference", minInterval: 3.0, burstLimit: 10, burstWindow: 60) // 1 req/3 sec
    ]

    // MARK: - Singleton

    static let shared = RateLimiter()

    private init() {
        // Load default configs
        for config in Self.defaultConfigs {
            configs[config.name] = config
        }
    }

    // MARK: - Configuration

    /// Configure rate limiting for a provider
    func configureProvider(_ config: ProviderConfig) {
        configs[config.name] = config
    }

    // MARK: - Rate Limiting

    /// Wait if needed to respect rate limits for a provider
    /// Returns immediately if rate limit allows, otherwise sleeps until allowed
    func waitIfNeeded(for provider: String) async {
        let config = configs[provider] ?? ProviderConfig(
            name: provider,
            minInterval: 1.0,
            burstLimit: 60,
            burstWindow: 60
        )

        await enforceMinInterval(for: provider, interval: config.minInterval)
        await enforceBurstLimit(for: provider, limit: config.burstLimit, window: config.burstWindow)

        recordRequest(for: provider)
    }

    /// Check if a request can be made without waiting
    func canMakeRequest(for provider: String) -> Bool {
        let config = configs[provider] ?? ProviderConfig(
            name: provider,
            minInterval: 1.0,
            burstLimit: 60,
            burstWindow: 60
        )

        // Check min interval
        if let lastRequest = lastRequestTimes[provider] {
            let elapsed = Date().timeIntervalSince(lastRequest)
            if elapsed < config.minInterval {
                return false
            }
        }

        // Check burst limit
        let now = Date()
        let windowStart = now.addingTimeInterval(-config.burstWindow)

        if let requests = requestCounts[provider] {
            let recentRequests = requests.filter { $0 > windowStart }
            if recentRequests.count >= config.burstLimit {
                return false
            }
        }

        return true
    }

    /// Get estimated wait time until next request is allowed
    func estimatedWaitTime(for provider: String) -> TimeInterval {
        let config = configs[provider] ?? ProviderConfig(
            name: provider,
            minInterval: 1.0,
            burstLimit: 60,
            burstWindow: 60
        )

        var maxWait: TimeInterval = 0

        // Check min interval wait
        if let lastRequest = lastRequestTimes[provider] {
            let elapsed = Date().timeIntervalSince(lastRequest)
            if elapsed < config.minInterval {
                maxWait = max(maxWait, config.minInterval - elapsed)
            }
        }

        // Check burst limit wait
        let now = Date()
        let windowStart = now.addingTimeInterval(-config.burstWindow)

        if let requests = requestCounts[provider] {
            let recentRequests = requests.filter { $0 > windowStart }.sorted()
            if recentRequests.count >= config.burstLimit {
                // Need to wait until oldest request falls out of window
                if let oldestInWindow = recentRequests.first {
                    let waitUntil = oldestInWindow.addingTimeInterval(config.burstWindow)
                    let wait = waitUntil.timeIntervalSince(now)
                    maxWait = max(maxWait, wait)
                }
            }
        }

        return maxWait
    }

    /// Reset rate limit tracking for a provider
    func reset(for provider: String) {
        lastRequestTimes.removeValue(forKey: provider)
        requestCounts.removeValue(forKey: provider)
    }

    /// Reset all rate limit tracking
    func resetAll() {
        lastRequestTimes.removeAll()
        requestCounts.removeAll()
    }

    // MARK: - Private Helpers

    private func enforceMinInterval(for provider: String, interval: TimeInterval) async {
        if let lastRequest = lastRequestTimes[provider] {
            let elapsed = Date().timeIntervalSince(lastRequest)
            if elapsed < interval {
                let waitTime = interval - elapsed
                try? await Task.sleep(nanoseconds: UInt64(waitTime * 1_000_000_000))
            }
        }
    }

    private func enforceBurstLimit(for provider: String, limit: Int, window: TimeInterval) async {
        let now = Date()
        let windowStart = now.addingTimeInterval(-window)

        // Clean up old requests
        if var requests = requestCounts[provider] {
            requests = requests.filter { $0 > windowStart }
            requestCounts[provider] = requests

            // Check if at limit
            if requests.count >= limit {
                // Wait until oldest request falls out of window
                if let oldestInWindow = requests.sorted().first {
                    let waitUntil = oldestInWindow.addingTimeInterval(window)
                    let waitTime = waitUntil.timeIntervalSince(now)
                    if waitTime > 0 {
                        try? await Task.sleep(nanoseconds: UInt64(waitTime * 1_000_000_000))
                    }
                }
            }
        }
    }

    private func recordRequest(for provider: String) {
        let now = Date()
        lastRequestTimes[provider] = now

        if requestCounts[provider] == nil {
            requestCounts[provider] = []
        }
        requestCounts[provider]?.append(now)

        // Clean up old requests periodically
        if let requests = requestCounts[provider], requests.count > 1000 {
            let oneHourAgo = now.addingTimeInterval(-3600)
            requestCounts[provider] = requests.filter { $0 > oneHourAgo }
        }
    }
}
