//
//  MockAppDataProvider.swift
//  SportsTimeTests
//
//  Mock implementation of AppDataProvider for testing without SwiftData dependencies.
//

import Foundation
import Combine
@testable import SportsTime

// MARK: - Mock App Data Provider

@MainActor
final class MockAppDataProvider: ObservableObject {

    // MARK: - Published State

    @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?

    // MARK: - Internal Storage

    private var teamsById: [String: Team] = [:]
    private var stadiumsById: [String: Stadium] = [:]
    private var dynamicSportsById: [String: DynamicSport] = [:]
    private var games: [Game] = []
    private var gamesById: [String: Game] = [:]

    // MARK: - Configuration

    struct Configuration {
        var simulatedLatency: TimeInterval = 0
        var shouldFailOnLoad: Bool = false
        var shouldFailOnFetch: Bool = false
        var isEmpty: Bool = false

        static var `default`: Configuration { Configuration() }
        static var empty: Configuration { Configuration(isEmpty: true) }
        static var failing: Configuration { Configuration(shouldFailOnLoad: true) }
        static var slow: Configuration { Configuration(simulatedLatency: 1.0) }
    }

    private var config: Configuration

    // MARK: - Call Tracking

    private(set) var loadInitialDataCallCount = 0
    private(set) var filterGamesCallCount = 0
    private(set) var filterRichGamesCallCount = 0
    private(set) var allGamesCallCount = 0
    private(set) var allRichGamesCallCount = 0

    // MARK: - Initialization

    init(config: Configuration = .default) {
        self.config = config
    }

    // MARK: - Configuration Methods

    func configure(_ newConfig: Configuration) {
        self.config = newConfig
    }

    func setTeams(_ newTeams: [Team]) {
        self.teams = newTeams
        self.teamsById = Dictionary(uniqueKeysWithValues: newTeams.map { ($0.id, $0) })
    }

    func setStadiums(_ newStadiums: [Stadium]) {
        self.stadiums = newStadiums
        self.stadiumsById = Dictionary(uniqueKeysWithValues: newStadiums.map { ($0.id, $0) })
    }

    func setGames(_ newGames: [Game]) {
        self.games = newGames
        self.gamesById = Dictionary(uniqueKeysWithValues: newGames.map { ($0.id, $0) })
    }

    func setDynamicSports(_ newSports: [DynamicSport]) {
        self.dynamicSports = newSports
        self.dynamicSportsById = Dictionary(uniqueKeysWithValues: newSports.map { ($0.id, $0) })
    }

    func reset() {
        teams = []
        stadiums = []
        dynamicSports = []
        games = []
        teamsById = [:]
        stadiumsById = [:]
        dynamicSportsById = [:]
        gamesById = [:]
        isLoading = false
        error = nil
        errorMessage = nil
        loadInitialDataCallCount = 0
        filterGamesCallCount = 0
        filterRichGamesCallCount = 0
        allGamesCallCount = 0
        allRichGamesCallCount = 0
        config = .default
    }

    // MARK: - Simulated Network

    private func simulateLatency() async {
        if config.simulatedLatency > 0 {
            try? await Task.sleep(nanoseconds: UInt64(config.simulatedLatency * 1_000_000_000))
        }
    }

    // MARK: - Data Loading

    func loadInitialData() async {
        loadInitialDataCallCount += 1

        if config.isEmpty {
            teams = []
            stadiums = []
            return
        }

        isLoading = true
        error = nil
        errorMessage = nil

        await simulateLatency()

        if config.shouldFailOnLoad {
            error = DataProviderError.contextNotConfigured
            errorMessage = "Mock load failure"
            isLoading = false
            return
        }

        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 Filtering (Local Queries)

    func filterGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [Game] {
        filterGamesCallCount += 1
        await simulateLatency()

        if config.shouldFailOnFetch {
            throw DataProviderError.contextNotConfigured
        }

        return games.filter { game in
            sports.contains(game.sport) &&
            game.dateTime >= startDate &&
            game.dateTime <= endDate
        }.sorted { $0.dateTime < $1.dateTime }
    }

    func allGames(for sports: Set<Sport>) async throws -> [Game] {
        allGamesCallCount += 1
        await simulateLatency()

        if config.shouldFailOnFetch {
            throw DataProviderError.contextNotConfigured
        }

        return games.filter { game in
            sports.contains(game.sport)
        }.sorted { $0.dateTime < $1.dateTime }
    }

    func fetchGame(by id: String) async throws -> Game? {
        await simulateLatency()

        if config.shouldFailOnFetch {
            throw DataProviderError.contextNotConfigured
        }

        return gamesById[id]
    }

    func filterRichGames(sports: Set<Sport>, startDate: Date, endDate: Date) async throws -> [RichGame] {
        filterRichGamesCallCount += 1
        let filteredGames = try await filterGames(sports: sports, startDate: startDate, endDate: endDate)

        return filteredGames.compactMap { game in
            richGame(from: game)
        }
    }

    func allRichGames(for sports: Set<Sport>) async throws -> [RichGame] {
        allRichGamesCallCount += 1
        let allFilteredGames = try await allGames(for: sports)

        return allFilteredGames.compactMap { game in
            richGame(from: game)
        }
    }

    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: - Convenience Extensions

extension MockAppDataProvider {
    /// Load fixture data from FixtureGenerator
    func loadFixtures(_ data: FixtureGenerator.GeneratedData) {
        setTeams(data.teams)
        setStadiums(data.stadiums)
        setGames(data.games)
    }

    /// Create a mock provider with fixture data pre-loaded
    static func withFixtures(_ config: FixtureGenerator.Configuration = .default) -> MockAppDataProvider {
        let mock = MockAppDataProvider()
        let data = FixtureGenerator.generate(with: config)
        mock.loadFixtures(data)
        return mock
    }

    /// Create a mock provider configured as empty
    static var empty: MockAppDataProvider {
        MockAppDataProvider(config: .empty)
    }

    /// Create a mock provider configured to fail
    static var failing: MockAppDataProvider {
        MockAppDataProvider(config: .failing)
    }
}

// MARK: - Test Helpers

extension MockAppDataProvider {
    /// Add a single game
    func addGame(_ game: Game) {
        games.append(game)
        gamesById[game.id] = game
    }

    /// Add a single team
    func addTeam(_ team: Team) {
        teams.append(team)
        teamsById[team.id] = team
    }

    /// Add a single stadium
    func addStadium(_ stadium: Stadium) {
        stadiums.append(stadium)
        stadiumsById[stadium.id] = stadium
    }

    /// Get all stored games (for test verification)
    func getAllStoredGames() -> [Game] {
        games
    }

    /// Get games count
    var gamesCount: Int { games.count }

    /// Get teams count
    var teamsCount: Int { teams.count }

    /// Get stadiums count
    var stadiumsCount: Int { stadiums.count }

    /// Add a single dynamic sport
    func addDynamicSport(_ sport: DynamicSport) {
        dynamicSports.append(sport)
        dynamicSportsById[sport.id] = sport
    }

    /// Get dynamic sports count
    var dynamicSportsCount: Int { dynamicSports.count }
}
