//
//  MockCloudKitService.swift
//  SportsTimeTests
//
//  Mock implementation of CloudKitService for testing without network dependencies.
//

import Foundation
@testable import SportsTime

// MARK: - Mock CloudKit Service

actor MockCloudKitService {

    // MARK: - Configuration

    struct Configuration {
        var isAvailable: Bool = true
        var simulatedLatency: TimeInterval = 0
        var shouldFailWithError: CloudKitError? = nil
        var errorAfterNCalls: Int? = nil

        static var `default`: Configuration { Configuration() }
        static var offline: Configuration { Configuration(isAvailable: false) }
        static var slow: Configuration { Configuration(simulatedLatency: 2.0) }
    }

    // MARK: - Stored Data

    private var stadiums: [Stadium] = []
    private var teams: [Team] = []
    private var games: [Game] = []
    private var leagueStructure: [LeagueStructureModel] = []
    private var teamAliases: [TeamAlias] = []
    private var stadiumAliases: [StadiumAlias] = []

    // MARK: - Call Tracking

    private(set) var fetchStadiumsCallCount = 0
    private(set) var fetchTeamsCallCount = 0
    private(set) var fetchGamesCallCount = 0
    private(set) var isAvailableCallCount = 0

    // MARK: - Configuration

    private var config: Configuration

    // MARK: - Initialization

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

    // MARK: - Configuration Methods

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

    func setStadiums(_ stadiums: [Stadium]) {
        self.stadiums = stadiums
    }

    func setTeams(_ teams: [Team]) {
        self.teams = teams
    }

    func setGames(_ games: [Game]) {
        self.games = games
    }

    func setLeagueStructure(_ structure: [LeagueStructureModel]) {
        self.leagueStructure = structure
    }

    func reset() {
        stadiums = []
        teams = []
        games = []
        leagueStructure = []
        teamAliases = []
        stadiumAliases = []
        fetchStadiumsCallCount = 0
        fetchTeamsCallCount = 0
        fetchGamesCallCount = 0
        isAvailableCallCount = 0
        config = .default
    }

    // MARK: - Simulated Network

    private func simulateNetwork() async throws {
        // Simulate latency
        if config.simulatedLatency > 0 {
            try await Task.sleep(nanoseconds: UInt64(config.simulatedLatency * 1_000_000_000))
        }

        // Check for configured error
        if let error = config.shouldFailWithError {
            throw error
        }
    }

    private func checkErrorAfterNCalls(_ callCount: Int) throws {
        if let errorAfterN = config.errorAfterNCalls, callCount >= errorAfterN {
            throw config.shouldFailWithError ?? CloudKitError.networkUnavailable
        }
    }

    // MARK: - Availability

    func isAvailable() async -> Bool {
        isAvailableCallCount += 1
        return config.isAvailable
    }

    func checkAvailabilityWithError() async throws {
        if !config.isAvailable {
            throw CloudKitError.networkUnavailable
        }
        if let error = config.shouldFailWithError {
            throw error
        }
    }

    // MARK: - Fetch Operations

    func fetchStadiums() async throws -> [Stadium] {
        fetchStadiumsCallCount += 1
        try checkErrorAfterNCalls(fetchStadiumsCallCount)
        try await simulateNetwork()
        return stadiums
    }

    func fetchTeams(for sport: Sport) async throws -> [Team] {
        fetchTeamsCallCount += 1
        try checkErrorAfterNCalls(fetchTeamsCallCount)
        try await simulateNetwork()
        return teams.filter { $0.sport == sport }
    }

    func fetchGames(
        sports: Set<Sport>,
        startDate: Date,
        endDate: Date
    ) async throws -> [Game] {
        fetchGamesCallCount += 1
        try checkErrorAfterNCalls(fetchGamesCallCount)
        try await simulateNetwork()

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

    func fetchGame(by id: String) async throws -> Game? {
        try await simulateNetwork()
        return games.first { $0.id == id }
    }

    // MARK: - Sync Fetch Methods (Delta Sync Pattern)

    /// Fetch stadiums for sync - returns all if lastSync is nil, otherwise filters by modification date
    func fetchStadiumsForSync(since lastSync: Date?) async throws -> [CloudKitService.SyncStadium] {
        try await simulateNetwork()
        // Mock doesn't track modification dates, so return all stadiums
        // (In production, CloudKit filters by modificationDate)
        return stadiums.map { stadium in
            CloudKitService.SyncStadium(
                stadium: stadium,
                canonicalId: stadium.id
            )
        }
    }

    /// Fetch teams for sync - returns all if lastSync is nil, otherwise filters by modification date
    func fetchTeamsForSync(since lastSync: Date?) async throws -> [CloudKitService.SyncTeam] {
        try await simulateNetwork()
        // Mock doesn't track modification dates, so return all teams
        // (In production, CloudKit filters by modificationDate)
        return teams.map { team in
            CloudKitService.SyncTeam(
                team: team,
                canonicalId: team.id,
                stadiumCanonicalId: team.stadiumId
            )
        }
    }

    /// Fetch games for sync - returns all if lastSync is nil, otherwise filters by modification date
    func fetchGamesForSync(since lastSync: Date?) async throws -> [CloudKitService.SyncGame] {
        try await simulateNetwork()
        // Mock doesn't track modification dates, so return all games
        // (In production, CloudKit filters by modificationDate)
        return games.map { game in
            CloudKitService.SyncGame(
                game: game,
                canonicalId: game.id,
                homeTeamCanonicalId: game.homeTeamId,
                awayTeamCanonicalId: game.awayTeamId,
                stadiumCanonicalId: game.stadiumId
            )
        }
    }

    // MARK: - League Structure & Aliases

    func fetchLeagueStructure(for sport: Sport? = nil) async throws -> [LeagueStructureModel] {
        try await simulateNetwork()
        if let sport = sport {
            return leagueStructure.filter { $0.sport == sport.rawValue }
        }
        return leagueStructure
    }

    func fetchTeamAliases(for teamCanonicalId: String? = nil) async throws -> [TeamAlias] {
        try await simulateNetwork()
        if let teamId = teamCanonicalId {
            return teamAliases.filter { $0.teamCanonicalId == teamId }
        }
        return teamAliases
    }

    func fetchStadiumAliases(for stadiumCanonicalId: String? = nil) async throws -> [StadiumAlias] {
        try await simulateNetwork()
        if let stadiumId = stadiumCanonicalId {
            return stadiumAliases.filter { $0.stadiumCanonicalId == stadiumId }
        }
        return stadiumAliases
    }

    // MARK: - Delta Sync

    func fetchLeagueStructureChanges(since lastSync: Date?) async throws -> [LeagueStructureModel] {
        try await simulateNetwork()
        guard let lastSync = lastSync else {
            return leagueStructure
        }
        return leagueStructure.filter { $0.lastModified > lastSync }
    }

    func fetchTeamAliasChanges(since lastSync: Date?) async throws -> [TeamAlias] {
        try await simulateNetwork()
        guard let lastSync = lastSync else {
            return teamAliases
        }
        return teamAliases.filter { $0.lastModified > lastSync }
    }

    func fetchStadiumAliasChanges(since lastSync: Date?) async throws -> [StadiumAlias] {
        try await simulateNetwork()
        guard let lastSync = lastSync else {
            return stadiumAliases
        }
        return stadiumAliases.filter { $0.lastModified > lastSync }
    }

    // MARK: - Subscriptions (No-ops for testing)

    func subscribeToScheduleUpdates() async throws {}
    func subscribeToLeagueStructureUpdates() async throws {}
    func subscribeToTeamAliasUpdates() async throws {}
    func subscribeToStadiumAliasUpdates() async throws {}
    func subscribeToAllUpdates() async throws {}
}

// MARK: - Convenience Extensions

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

    /// Configure to simulate specific error scenarios
    static func withError(_ error: CloudKitError) -> MockCloudKitService {
        let mock = MockCloudKitService()
        Task {
            await mock.configure(Configuration(shouldFailWithError: error))
        }
        return mock
    }

    /// Configure to be offline
    static var offline: MockCloudKitService {
        MockCloudKitService(config: .offline)
    }
}
