//
//  FreeScoreAPI.swift
//  SportsTime
//
//  Multi-provider score resolution facade using FREE data sources only.
//

import Foundation

// MARK: - Provider Reliability

enum ProviderReliability: String, Sendable {
    case official    // MLB Stats, NHL Stats - stable, documented
    case unofficial  // ESPN API - works but may break
    case scraped     // Sports-Reference - HTML parsing, fragile
}

// MARK: - Historical Game Query

struct HistoricalGameQuery: Sendable {
    let sport: Sport
    let date: Date
    let homeTeamAbbrev: String?
    let awayTeamAbbrev: String?
    let stadiumCanonicalId: String?

    init(
        sport: Sport,
        date: Date,
        homeTeamAbbrev: String? = nil,
        awayTeamAbbrev: String? = nil,
        stadiumCanonicalId: String? = nil
    ) {
        self.sport = sport
        self.date = date
        self.homeTeamAbbrev = homeTeamAbbrev
        self.awayTeamAbbrev = awayTeamAbbrev
        self.stadiumCanonicalId = stadiumCanonicalId
    }

    /// Normalized date string for matching
    var normalizedDateString: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.timeZone = TimeZone(identifier: "America/New_York")
        return formatter.string(from: date)
    }
}

// MARK: - Historical Game Result

struct HistoricalGameResult: Sendable {
    let sport: Sport
    let gameDate: Date
    let homeTeamAbbrev: String
    let awayTeamAbbrev: String
    let homeTeamName: String
    let awayTeamName: String
    let homeScore: Int?
    let awayScore: Int?
    let source: ScoreSource
    let providerName: String

    var scoreString: String? {
        guard let home = homeScore, let away = awayScore else { return nil }
        return "\(away)-\(home)"
    }

    var hasScore: Bool {
        homeScore != nil && awayScore != nil
    }
}

// MARK: - Score Resolution Result

enum ScoreResolutionResult: Sendable {
    case resolved(HistoricalGameResult)
    case pending                      // Background retry queued
    case requiresUserInput(reason: String)  // All tiers failed
    case notFound(reason: String)    // No game matched query

    var isResolved: Bool {
        if case .resolved = self { return true }
        return false
    }

    var result: HistoricalGameResult? {
        if case .resolved(let result) = self { return result }
        return nil
    }
}

// MARK: - Score API Provider Protocol

protocol ScoreAPIProvider: Sendable {
    var name: String { get }
    var supportedSports: Set<Sport> { get }
    var reliability: ProviderReliability { get }
    var rateLimitKey: String { get }

    func fetchGame(query: HistoricalGameQuery) async throws -> HistoricalGameResult?
}

// MARK: - Provider Errors

enum ScoreProviderError: Error, LocalizedError, Sendable {
    case networkError(underlying: String)
    case rateLimited
    case parseError(message: String)
    case gameNotFound
    case unsupportedSport(Sport)
    case providerUnavailable(reason: String)

    var errorDescription: String? {
        switch self {
        case .networkError(let underlying):
            return "Network error: \(underlying)"
        case .rateLimited:
            return "Rate limited by provider"
        case .parseError(let message):
            return "Failed to parse response: \(message)"
        case .gameNotFound:
            return "Game not found"
        case .unsupportedSport(let sport):
            return "\(sport.rawValue) not supported by this provider"
        case .providerUnavailable(let reason):
            return "Provider unavailable: \(reason)"
        }
    }
}

// MARK: - Free Score API Orchestrator

@MainActor
final class FreeScoreAPI {

    // MARK: - Properties

    static let shared = FreeScoreAPI()

    private var providers: [ScoreAPIProvider] = []
    private var disabledProviders: [String: Date] = [:]  // provider → disabled until
    private var failureCounts: [String: Int] = [:]

    // Failure thresholds
    private let officialFailureThreshold = Int.max  // Never auto-disable
    private let unofficialFailureThreshold = 3
    private let scrapedFailureThreshold = 2
    private let disableDuration: TimeInterval = 24 * 60 * 60  // 24 hours
    private let failureWindowDuration: TimeInterval = 60 * 60  // 1 hour

    private let rateLimiter = RateLimiter.shared

    // MARK: - Initialization

    private init() {
        // Register providers in priority order
        registerDefaultProviders()
    }

    private func registerDefaultProviders() {
        // Official APIs first (most reliable)
        providers.append(MLBStatsProvider())
        providers.append(NHLStatsProvider())
        providers.append(NBAStatsProvider())

        // Note: ESPN provider could be added here as unofficial fallback
        // Note: Sports-Reference scraper could be added as last resort
    }

    // MARK: - Public API

    /// Register a custom provider
    func registerProvider(_ provider: ScoreAPIProvider) {
        providers.append(provider)
    }

    /// Resolve score for a game query
    /// Tries each provider in order: official > unofficial > scraped
    func resolveScore(query: HistoricalGameQuery) async -> ScoreResolutionResult {
        // Filter providers that support this sport
        let eligibleProviders = providers.filter {
            $0.supportedSports.contains(query.sport) && !isDisabled($0)
        }

        guard !eligibleProviders.isEmpty else {
            return .requiresUserInput(reason: "No providers available for \(query.sport.rawValue)")
        }

        // Sort by reliability (official first)
        let sortedProviders = eligibleProviders.sorted { p1, p2 in
            reliabilityOrder(p1.reliability) < reliabilityOrder(p2.reliability)
        }

        // Try each provider in order
        for provider in sortedProviders {
            do {
                // Wait for rate limit
                await rateLimiter.waitIfNeeded(for: provider.rateLimitKey)

                // Attempt fetch
                if let result = try await provider.fetchGame(query: query) {
                    // Success - reset failure count
                    resetFailureCount(for: provider)
                    return .resolved(result)
                }
            } catch {
                // Record failure
                recordFailure(for: provider, error: error)

                // Continue to next provider if this one failed
                continue
            }
        }

        // All providers failed or returned nil
        return .notFound(reason: "Game not found in any provider for \(query.sport.rawValue) on \(query.normalizedDateString)")
    }

    /// Check if a provider is available
    func isProviderAvailable(_ providerName: String) -> Bool {
        guard let provider = providers.first(where: { $0.name == providerName }) else {
            return false
        }
        return !isDisabled(provider)
    }

    /// Get list of available providers for a sport
    func availableProviders(for sport: Sport) -> [String] {
        providers
            .filter { $0.supportedSports.contains(sport) && !isDisabled($0) }
            .map { $0.name }
    }

    /// Manually re-enable a disabled provider
    func enableProvider(_ providerName: String) {
        disabledProviders.removeValue(forKey: providerName)
        failureCounts.removeValue(forKey: providerName)
    }

    /// Manually disable a provider
    func disableProvider(_ providerName: String, until date: Date) {
        disabledProviders[providerName] = date
    }

    // MARK: - Provider Management

    private func isDisabled(_ provider: ScoreAPIProvider) -> Bool {
        guard let disabledUntil = disabledProviders[provider.name] else {
            return false
        }

        // Check if disable period has expired
        if Date() > disabledUntil {
            disabledProviders.removeValue(forKey: provider.name)
            return false
        }

        return true
    }

    private func recordFailure(for provider: ScoreAPIProvider, error: Error) {
        let count = (failureCounts[provider.name] ?? 0) + 1
        failureCounts[provider.name] = count

        // Check if should auto-disable
        let threshold = failureThreshold(for: provider.reliability)

        if count >= threshold {
            let disableUntil = Date().addingTimeInterval(disableDuration)
            disabledProviders[provider.name] = disableUntil
            failureCounts.removeValue(forKey: provider.name)
        }
    }

    private func resetFailureCount(for provider: ScoreAPIProvider) {
        failureCounts.removeValue(forKey: provider.name)
    }

    private func failureThreshold(for reliability: ProviderReliability) -> Int {
        switch reliability {
        case .official:
            return officialFailureThreshold
        case .unofficial:
            return unofficialFailureThreshold
        case .scraped:
            return scrapedFailureThreshold
        }
    }

    private func reliabilityOrder(_ reliability: ProviderReliability) -> Int {
        switch reliability {
        case .official: return 0
        case .unofficial: return 1
        case .scraped: return 2
        }
    }
}
