//
//  DataProvider.swift
//  SportsTime
//
//  Unified data provider that reads from SwiftData for offline support.
//  Data is bootstrapped from bundled JSON and can be synced via CloudKit.
//

import Foundation
import SwiftData
import Combine

/// Environment-aware data provider that reads from SwiftData (offline-first)
@MainActor
final class AppDataProvider: ObservableObject {
    static let shared = AppDataProvider()

    @Published private(set) var teams: [Team] = []
    @Published private(set) var stadiums: [Stadium] = []
    @Published private(set) var dynamicSports: [DynamicSport] = []
    @Published private(set) var isLoading = false
    @Published private(set) var error: Error?
    @Published private(set) var errorMessage: String?

    // Lookup dictionaries - keyed by canonical ID (String)
    private var teamsById: [String: Team] = [:]
    private var stadiumsById: [String: Stadium] = [:]
    private var dynamicSportsById: [String: DynamicSport] = [:]

    private var modelContext: ModelContext?

    private init() {}

    // MARK: - Configuration

    /// Set the model context for SwiftData access
    func configure(with context: ModelContext) {
        self.modelContext = context
    }

    // MARK: - Data Loading

    /// Load all data from SwiftData into memory for fast access
    func loadInitialData() async {
        guard let context = modelContext else {
            errorMessage = "Model context not configured"
            return
        }

        isLoading = true
        error = nil
        errorMessage = nil

        do {
            // Fetch canonical stadiums from SwiftData
            let stadiumDescriptor = FetchDescriptor<CanonicalStadium>(
                predicate: #Predicate { $0.deprecatedAt == nil }
            )
            let canonicalStadiums = try context.fetch(stadiumDescriptor)

            // Convert to domain models and build lookups
            var loadedStadiums: [Stadium] = []
            var stadiumLookup: [String: Stadium] = [:]
            for canonical in canonicalStadiums {
                let stadium = canonical.toDomain()
                loadedStadiums.append(stadium)
                stadiumLookup[stadium.id] = stadium
            }

            // Fetch canonical teams from SwiftData
            let teamDescriptor = FetchDescriptor<CanonicalTeam>(
                predicate: #Predicate { $0.deprecatedAt == nil }
            )
            let canonicalTeams = try context.fetch(teamDescriptor)

            // Convert to domain models
            var loadedTeams: [Team] = []
            var teamLookup: [String: Team] = [:]
            for canonical in canonicalTeams {
                let team = canonical.toDomain()
                loadedTeams.append(team)
                teamLookup[team.id] = team
            }

            // Fetch canonical sports from SwiftData
            let sportDescriptor = FetchDescriptor<CanonicalSport>(
                predicate: #Predicate { $0.isActive == true }
            )
            let canonicalSports = try context.fetch(sportDescriptor)

            // Convert to domain models
            var loadedSports: [DynamicSport] = []
            var sportLookup: [String: DynamicSport] = [:]
            for canonical in canonicalSports {
                let sport = canonical.toDomain()
                loadedSports.append(sport)
                sportLookup[sport.id] = sport
            }

            self.teams = loadedTeams
            self.stadiums = loadedStadiums
            self.dynamicSports = loadedSports
            self.teamsById = teamLookup
            self.stadiumsById = stadiumLookup
            self.dynamicSportsById = sportLookup

        } catch {
            self.error = error
            self.errorMessage = error.localizedDescription
        }

        isLoading = false
    }

    func clearError() {
        error = nil
        errorMessage = nil
    }

    func retry() async {
        await loadInitialData()
    }

    // MARK: - Data Access

    func team(for id: String) -> Team? {
        teamsById[id]
    }

    func stadium(for id: String) -> Stadium? {
        stadiumsById[id]
    }

    func teams(for sport: Sport) -> [Team] {
        teams.filter { $0.sport == sport }
    }

    func dynamicSport(for id: String) -> DynamicSport? {
        dynamicSportsById[id]
    }

    /// All sports: built-in Sport enum cases + CloudKit-defined DynamicSports
    var allSports: [any AnySport] {
        let builtIn: [any AnySport] = Sport.allCases
        let dynamic: [any AnySport] = dynamicSports
        return builtIn + dynamic
    }

    // MARK: - Game Fetching

    /// Filter games from SwiftData within date range
    func filterGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game] {
        guard let context = modelContext else {
            throw DataProviderError.contextNotConfigured
        }

        let sportStrings = sports.map { $0.rawValue }

        let descriptor = FetchDescriptor<CanonicalGame>(
            predicate: #Predicate<CanonicalGame> { game in
                game.deprecatedAt == nil &&
                game.dateTime >= startDate &&
                game.dateTime <= endDate
            }
        )

        let canonicalGames = try context.fetch(descriptor)

        // Filter by sport and convert to domain models
        return canonicalGames.compactMap { canonical -> Game? in
            guard sportStrings.contains(canonical.sport) else { return nil }
            return canonical.toDomain()
        }
    }

    /// Get all games for specified sports (no date filtering)
    func allGames(for sports: Set<Sport>) async throws -> [Game] {
        guard let context = modelContext else {
            throw DataProviderError.contextNotConfigured
        }

        let sportStrings = sports.map { $0.rawValue }

        let descriptor = FetchDescriptor<CanonicalGame>(
            predicate: #Predicate<CanonicalGame> { game in
                game.deprecatedAt == nil
            },
            sortBy: [SortDescriptor(\.dateTime)]
        )

        let canonicalGames = try context.fetch(descriptor)

        return canonicalGames.compactMap { canonical -> Game? in
            guard sportStrings.contains(canonical.sport) else { return nil }
            return canonical.toDomain()
        }
    }

    /// Fetch a single game by canonical ID
    func fetchGame(by id: String) async throws -> Game? {
        guard let context = modelContext else {
            throw DataProviderError.contextNotConfigured
        }

        let descriptor = FetchDescriptor<CanonicalGame>(
            predicate: #Predicate<CanonicalGame> { $0.canonicalId == id }
        )

        guard let canonical = try context.fetch(descriptor).first else {
            return nil
        }

        return canonical.toDomain()
    }

    /// Filter games with full team and stadium data within date range
    func filterRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
        let games = try await filterGames(sports: sports, startDate: startDate, endDate: endDate)

        return games.compactMap { game in
            guard let homeTeam = teamsById[game.homeTeamId],
                  let awayTeam = teamsById[game.awayTeamId],
                  let stadium = stadiumsById[game.stadiumId] else {
                return nil
            }
            return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
        }
    }

    /// Get all games with full team and stadium data (no date filtering)
    func allRichGames(for sports: Set<Sport>) async throws -> [RichGame] {
        let games = try await allGames(for: sports)

        return games.compactMap { game in
            guard let homeTeam = teamsById[game.homeTeamId],
                  let awayTeam = teamsById[game.awayTeamId],
                  let stadium = stadiumsById[game.stadiumId] else {
                return nil
            }
            return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
        }
    }

    func richGame(from game: Game) -> RichGame? {
        guard let homeTeam = teamsById[game.homeTeamId],
              let awayTeam = teamsById[game.awayTeamId],
              let stadium = stadiumsById[game.stadiumId] else {
            return nil
        }
        return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
    }

    /// Get all games for a specific team (home or away) - for lazy loading in game picker
    func gamesForTeam(teamId: String) async throws -> [RichGame] {
        guard let context = modelContext else {
            throw DataProviderError.contextNotConfigured
        }

        // Fetch all non-deprecated games (predicate with captured vars causes type-check timeout)
        let descriptor = FetchDescriptor<CanonicalGame>(
            predicate: #Predicate<CanonicalGame> { $0.deprecatedAt == nil },
            sortBy: [SortDescriptor(\.dateTime)]
        )

        let allCanonical: [CanonicalGame] = try context.fetch(descriptor)

        // Filter by team in Swift (fast for ~5K games)
        var teamGames: [RichGame] = []
        for canonical in allCanonical {
            guard canonical.homeTeamCanonicalId == teamId || canonical.awayTeamCanonicalId == teamId else {
                continue
            }
            let game = canonical.toDomain()
            guard let homeTeam = teamsById[game.homeTeamId],
                  let awayTeam = teamsById[game.awayTeamId],
                  let stadium = stadiumsById[game.stadiumId] else {
                continue
            }
            teamGames.append(RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium))
        }
        return teamGames
    }
}

// MARK: - Errors

enum DataProviderError: Error, LocalizedError {
    case contextNotConfigured

    var errorDescription: String? {
        switch self {
        case .contextNotConfigured:
            return "Data provider not configured with model context"
        }
    }
}
