//
//  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 isLoading = false
    @Published private(set) var error: Error?
    @Published private(set) var errorMessage: String?

    private var teamsById: [UUID: Team] = [:]
    private var stadiumsById: [UUID: Stadium] = [:]
    private var stadiumsByCanonicalId: [String: Stadium] = [:]
    private var teamsByCanonicalId: [String: Team] = [:]

    // Canonical ID lookups for game conversion
    private var canonicalTeamUUIDs: [String: UUID] = [:]
    private var canonicalStadiumUUIDs: [String: UUID] = [:]

    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] = []
            for canonical in canonicalStadiums {
                let stadium = canonical.toDomain()
                loadedStadiums.append(stadium)
                stadiumsByCanonicalId[canonical.canonicalId] = stadium
                canonicalStadiumUUIDs[canonical.canonicalId] = stadium.id
            }

            // 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] = []
            for canonical in canonicalTeams {
                // Get stadium UUID for this team
                let stadiumUUID = canonicalStadiumUUIDs[canonical.stadiumCanonicalId] ?? UUID()
                let team = canonical.toDomain(stadiumUUID: stadiumUUID)
                loadedTeams.append(team)
                teamsByCanonicalId[canonical.canonicalId] = team
                canonicalTeamUUIDs[canonical.canonicalId] = team.id
            }

            self.teams = loadedTeams
            self.stadiums = loadedStadiums

            // Build lookup dictionaries (use reduce to handle potential duplicates gracefully)
            self.teamsById = loadedTeams.reduce(into: [:]) { dict, team in
                if dict[team.id] != nil {
                    print("⚠️ Duplicate team UUID: \(team.id) - \(team.name)")
                }
                dict[team.id] = team
            }
            self.stadiumsById = loadedStadiums.reduce(into: [:]) { dict, stadium in
                if dict[stadium.id] != nil {
                    print("⚠️ Duplicate stadium UUID: \(stadium.id) - \(stadium.name)")
                }
                dict[stadium.id] = stadium
            }

        } 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: UUID) -> Team? {
        teamsById[id]
    }

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

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

    // MARK: - Game Fetching

    /// Fetch games from SwiftData within date range
    func fetchGames(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
        let result = canonicalGames.compactMap { canonical -> Game? in
            guard sportStrings.contains(canonical.sport) else { return nil }

            let homeTeamUUID = canonicalTeamUUIDs[canonical.homeTeamCanonicalId] ?? UUID()
            let awayTeamUUID = canonicalTeamUUIDs[canonical.awayTeamCanonicalId] ?? UUID()
            let stadiumUUID = canonicalStadiumUUIDs[canonical.stadiumCanonicalId] ?? UUID()

            return canonical.toDomain(
                homeTeamUUID: homeTeamUUID,
                awayTeamUUID: awayTeamUUID,
                stadiumUUID: stadiumUUID
            )
        }

        return result
    }

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

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

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

        let homeTeamUUID = canonicalTeamUUIDs[canonical.homeTeamCanonicalId] ?? UUID()
        let awayTeamUUID = canonicalTeamUUIDs[canonical.awayTeamCanonicalId] ?? UUID()
        let stadiumUUID = canonicalStadiumUUIDs[canonical.stadiumCanonicalId] ?? UUID()

        return canonical.toDomain(
            homeTeamUUID: homeTeamUUID,
            awayTeamUUID: awayTeamUUID,
            stadiumUUID: stadiumUUID
        )
    }

    /// Fetch games with full team and stadium data
    func fetchRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
        let games = try await fetchGames(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)
        }
    }

    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)
    }
}

// MARK: - Errors

enum DataProviderError: Error, LocalizedError {
    case contextNotConfigured

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