//
//  AchievementEngineTests.swift
//  SportsTimeTests
//
//  TDD tests for AchievementEngine - all achievement requirement types.
//  Tests the bug fix where specificStadium achievements use symbolic IDs
//  (e.g., "stadium_mlb_bos") that need to be resolved to actual stadium UUIDs.
//

import XCTest
import SwiftUI
import SwiftData
@testable import SportsTime

/// Tests for AchievementEngine that don't require full AppDataProvider setup
@MainActor
final class AchievementEngineTests: XCTestCase {

    // MARK: - Basic Tests

    /// Verify the AchievementRegistry contains specificStadium achievements
    func test_registry_containsSpecificStadiumAchievements() {
        let fenwayAchievement = AchievementRegistry.achievement(byId: "special_fenway")
        let wrigleyAchievement = AchievementRegistry.achievement(byId: "special_wrigley")
        let msgAchievement = AchievementRegistry.achievement(byId: "special_msg")

        XCTAssertNotNil(fenwayAchievement, "Green Monster achievement should exist")
        XCTAssertNotNil(wrigleyAchievement, "Ivy League achievement should exist")
        XCTAssertNotNil(msgAchievement, "MSG achievement should exist")

        // Verify they use specificStadium requirement
        if case .specificStadium(let id) = fenwayAchievement!.requirement {
            XCTAssertEqual(id, "stadium_mlb_bos", "Fenway should use stadium_mlb_bos")
        } else {
            XCTFail("Fenway achievement should have specificStadium requirement")
        }

        if case .specificStadium(let id) = wrigleyAchievement!.requirement {
            XCTAssertEqual(id, "stadium_mlb_chc", "Wrigley should use stadium_mlb_chc")
        } else {
            XCTFail("Wrigley achievement should have specificStadium requirement")
        }

        if case .specificStadium(let id) = msgAchievement!.requirement {
            XCTAssertEqual(id, "stadium_nba_nyk", "MSG should use stadium_nba_nyk")
        } else {
            XCTFail("MSG achievement should have specificStadium requirement")
        }
    }

    /// Verify all achievements are defined in registry
    func test_registry_containsAllAchievementTypes() {
        let all = AchievementRegistry.all

        // Should have achievements for each type
        let hasFirstVisit = all.contains { if case .firstVisit = $0.requirement { return true }; return false }
        let hasVisitCount = all.contains { if case .visitCount = $0.requirement { return true }; return false }
        let hasSpecificStadium = all.contains { if case .specificStadium = $0.requirement { return true }; return false }
        let hasMultipleLeagues = all.contains { if case .multipleLeagues = $0.requirement { return true }; return false }
        let hasVisitsInDays = all.contains { if case .visitsInDays = $0.requirement { return true }; return false }

        XCTAssertTrue(hasFirstVisit, "Registry should have firstVisit achievement")
        XCTAssertTrue(hasVisitCount, "Registry should have visitCount achievement")
        XCTAssertTrue(hasSpecificStadium, "Registry should have specificStadium achievement")
        XCTAssertTrue(hasMultipleLeagues, "Registry should have multipleLeagues achievement")
        XCTAssertTrue(hasVisitsInDays, "Registry should have visitsInDays achievement")
    }

    /// Verify AchievementProgress isEarned logic
    func test_achievementProgress_isEarnedCalculation() {
        let definition = AchievementDefinition(
            id: "test",
            name: "Test",
            description: "Test",
            category: .special,
            sport: nil,
            iconName: "star",
            iconColor: .orange,
            requirement: .visitCount(5)
        )

        // 0/5 - not earned
        let progress0 = AchievementProgress(
            definition: definition,
            currentProgress: 0,
            totalRequired: 5,
            hasStoredAchievement: false,
            earnedAt: nil
        )
        XCTAssertFalse(progress0.isEarned)

        // 3/5 - not earned
        let progress3 = AchievementProgress(
            definition: definition,
            currentProgress: 3,
            totalRequired: 5,
            hasStoredAchievement: false,
            earnedAt: nil
        )
        XCTAssertFalse(progress3.isEarned)

        // 5/5 - earned (computed)
        let progress5 = AchievementProgress(
            definition: definition,
            currentProgress: 5,
            totalRequired: 5,
            hasStoredAchievement: false,
            earnedAt: nil
        )
        XCTAssertTrue(progress5.isEarned, "5/5 should be earned")

        // 3/5 but has stored achievement - earned
        let progressStored = AchievementProgress(
            definition: definition,
            currentProgress: 3,
            totalRequired: 5,
            hasStoredAchievement: true,
            earnedAt: Date()
        )
        XCTAssertTrue(progressStored.isEarned, "Should be earned if stored")
    }

    /// Verify AchievementProgress percentage calculation
    func test_achievementProgress_percentageCalculation() {
        let definition = AchievementDefinition(
            id: "test",
            name: "Test",
            description: "Test",
            category: .special,
            sport: nil,
            iconName: "star",
            iconColor: .orange,
            requirement: .visitCount(10)
        )

        let progress = AchievementProgress(
            definition: definition,
            currentProgress: 5,
            totalRequired: 10,
            hasStoredAchievement: false,
            earnedAt: nil
        )

        XCTAssertEqual(progress.progressPercentage, 0.5, accuracy: 0.001)
        XCTAssertEqual(progress.progressText, "5/10")
    }

    /// Verify AchievementDelta hasChanges
    func test_achievementDelta_hasChanges() {
        let definition = AchievementDefinition(
            id: "test",
            name: "Test",
            description: "Test",
            category: .special,
            sport: nil,
            iconName: "star",
            iconColor: .orange,
            requirement: .firstVisit
        )

        let emptyDelta = AchievementDelta(newlyEarned: [], revoked: [], stillEarned: [])
        XCTAssertFalse(emptyDelta.hasChanges, "Empty delta should have no changes")

        let newEarnedDelta = AchievementDelta(newlyEarned: [definition], revoked: [], stillEarned: [])
        XCTAssertTrue(newEarnedDelta.hasChanges, "Delta with newly earned should have changes")

        let revokedDelta = AchievementDelta(newlyEarned: [], revoked: [definition], stillEarned: [])
        XCTAssertTrue(revokedDelta.hasChanges, "Delta with revoked should have changes")

        let stillEarnedDelta = AchievementDelta(newlyEarned: [], revoked: [], stillEarned: [definition])
        XCTAssertFalse(stillEarnedDelta.hasChanges, "Delta with only stillEarned should have no changes")
    }
}

// MARK: - Integration Tests (require AppDataProvider)

/// Integration tests that test the full achievement engine with real data
@MainActor
final class AchievementEngineIntegrationTests: XCTestCase {

    var modelContainer: ModelContainer!
    var modelContext: ModelContext!

    // Canonical IDs for test stadiums
    let fenwayId = "stadium_mlb_bos"
    let wrigleyId = "stadium_mlb_chc"
    let msgId = "stadium_nba_nyk"

    override func setUp() async throws {
        try await super.setUp()

        // Create container with ALL models the app uses
        let schema = Schema([
            // User data models
            StadiumVisit.self,
            Achievement.self,
            VisitPhotoMetadata.self,
            CachedGameScore.self,
            // Canonical models
            CanonicalTeam.self,
            CanonicalStadium.self,
            CanonicalGame.self,
            LeagueStructureModel.self,
            TeamAlias.self,
            StadiumAlias.self,
            SyncState.self,
            // Trip models
            SavedTrip.self,
            TripVote.self,
            UserPreferences.self,
            CachedSchedule.self
        ])
        let config = ModelConfiguration(isStoredInMemoryOnly: true)
        modelContainer = try ModelContainer(for: schema, configurations: [config])
        modelContext = modelContainer.mainContext

        // Setup minimal test data
        await setupTestData()

        // Configure and load AppDataProvider
        AppDataProvider.shared.configure(with: modelContext)
        await AppDataProvider.shared.loadInitialData()
    }

    override func tearDown() async throws {
        modelContainer = nil
        modelContext = nil
        try await super.tearDown()
    }

    private func setupTestData() async {
        // Create Fenway Park stadium
        let fenway = CanonicalStadium(
            canonicalId: fenwayId,
            name: "Fenway Park",
            city: "Boston",
            state: "MA",
            latitude: 42.3467,
            longitude: -71.0972,
            capacity: 37755,
            yearOpened: 1912,
            sport: "mlb"
        )

        // Create Wrigley Field stadium
        let wrigley = CanonicalStadium(
            canonicalId: wrigleyId,
            name: "Wrigley Field",
            city: "Chicago",
            state: "IL",
            latitude: 41.9484,
            longitude: -87.6553,
            capacity: 41649,
            yearOpened: 1914,
            sport: "mlb"
        )

        // Create MSG stadium
        let msg = CanonicalStadium(
            canonicalId: msgId,
            name: "Madison Square Garden",
            city: "New York",
            state: "NY",
            latitude: 40.7505,
            longitude: -73.9934,
            capacity: 19812,
            yearOpened: 1968,
            sport: "nba"
        )

        modelContext.insert(fenway)
        modelContext.insert(wrigley)
        modelContext.insert(msg)

        // Create Red Sox team (plays at Fenway)
        let redSox = CanonicalTeam(
            canonicalId: "team_mlb_bos",
            name: "Red Sox",
            abbreviation: "BOS",
            sport: "mlb",
            city: "Boston",
            stadiumCanonicalId: "stadium_mlb_bos"
        )

        // Create Cubs team (plays at Wrigley)
        let cubs = CanonicalTeam(
            canonicalId: "team_mlb_chc",
            name: "Cubs",
            abbreviation: "CHC",
            sport: "mlb",
            city: "Chicago",
            stadiumCanonicalId: "stadium_mlb_chc"
        )

        // Create Knicks team (plays at MSG)
        let knicks = CanonicalTeam(
            canonicalId: "team_nba_nyk",
            name: "Knicks",
            abbreviation: "NYK",
            sport: "nba",
            city: "New York",
            stadiumCanonicalId: "stadium_nba_nyk"
        )

        modelContext.insert(redSox)
        modelContext.insert(cubs)
        modelContext.insert(knicks)

        try? modelContext.save()
    }

    /// Test: Visit Fenway Park, check Green Monster achievement progress
    func test_fenwayVisit_earnsGreenMonster() async throws {
        // Create engine
        let engine = AchievementEngine(modelContext: modelContext, dataProvider: AppDataProvider.shared)

        // Create a visit to Fenway using the canonical stadium ID
        let visit = StadiumVisit(
            stadiumId: fenwayId,
            stadiumNameAtVisit: "Fenway Park",
            visitDate: Date(),
            sport: .mlb
        )
        modelContext.insert(visit)
        try modelContext.save()

        // Get progress
        let progress = try await engine.getProgress()

        // Find Green Monster achievement
        let greenMonster = progress.first { $0.definition.id == "special_fenway" }
        XCTAssertNotNil(greenMonster, "Green Monster achievement should be in progress list")

        if let gm = greenMonster {
            XCTAssertEqual(gm.currentProgress, 1, "Progress should be 1 after visiting Fenway")
            XCTAssertEqual(gm.totalRequired, 1, "Total required should be 1")
            XCTAssertTrue(gm.isEarned, "Green Monster should be earned after visiting Fenway")
        }
    }

    /// Test: Visit Wrigley, check Ivy League achievement progress
    func test_wrigleyVisit_earnsIvyLeague() async throws {
        let engine = AchievementEngine(modelContext: modelContext, dataProvider: AppDataProvider.shared)

        let visit = StadiumVisit(
            stadiumId: wrigleyId,
            stadiumNameAtVisit: "Wrigley Field",
            visitDate: Date(),
            sport: .mlb
        )
        modelContext.insert(visit)
        try modelContext.save()

        let progress = try await engine.getProgress()
        let ivyLeague = progress.first { $0.definition.id == "special_wrigley" }

        XCTAssertNotNil(ivyLeague, "Ivy League achievement should exist")
        XCTAssertTrue(ivyLeague!.isEarned, "Ivy League should be earned after visiting Wrigley")
    }

    /// Test: No visits, specificStadium achievements show 0 progress
    func test_noVisits_showsZeroProgress() async throws {
        let engine = AchievementEngine(modelContext: modelContext, dataProvider: AppDataProvider.shared)

        let progress = try await engine.getProgress()

        let greenMonster = progress.first { $0.definition.id == "special_fenway" }
        XCTAssertNotNil(greenMonster)
        XCTAssertEqual(greenMonster!.currentProgress, 0, "Progress should be 0 with no visits")
        XCTAssertFalse(greenMonster!.isEarned, "Should not be earned with no visits")
    }

    /// Test: First visit achievement
    func test_firstVisit_earnsAchievement() async throws {
        let engine = AchievementEngine(modelContext: modelContext, dataProvider: AppDataProvider.shared)

        // No visits - first visit not earned
        var progress = try await engine.getProgress()
        var firstVisit = progress.first { $0.definition.id == "first_visit" }
        XCTAssertFalse(firstVisit!.isEarned, "First visit should not be earned initially")

        // Add a visit
        let visit = StadiumVisit(
            stadiumId: fenwayId,
            stadiumNameAtVisit: "Fenway Park",
            visitDate: Date(),
            sport: .mlb
        )
        modelContext.insert(visit)
        try modelContext.save()

        // Now first visit should be earned
        progress = try await engine.getProgress()
        firstVisit = progress.first { $0.definition.id == "first_visit" }
        XCTAssertTrue(firstVisit!.isEarned, "First visit should be earned after any visit")
    }

    /// Test: Multiple leagues achievement
    func test_multipleLeagues_requiresThreeSports() async throws {
        let engine = AchievementEngine(modelContext: modelContext, dataProvider: AppDataProvider.shared)

        // Add TD Garden for NHL
        let tdGardenId = "stadium_nhl_bos"
        let tdGarden = CanonicalStadium(
            canonicalId: tdGardenId,
            name: "TD Garden",
            city: "Boston",
            state: "MA",
            latitude: 42.3662,
            longitude: -71.0621,
            capacity: 17850,
            yearOpened: 1995,
            sport: "nhl"
        )
        modelContext.insert(tdGarden)

        let bruins = CanonicalTeam(
            canonicalId: "team_nhl_bos",
            name: "Bruins",
            abbreviation: "BOS",
            sport: "nhl",
            city: "Boston",
            stadiumCanonicalId: "stadium_nhl_bos"
        )
        modelContext.insert(bruins)
        try modelContext.save()

        // Reload data provider
        await AppDataProvider.shared.loadInitialData()

        // Visit MLB stadium only - not enough
        let mlbVisit = StadiumVisit(
            stadiumId: fenwayId,
            stadiumNameAtVisit: "Fenway Park",
            visitDate: Date(),
            sport: .mlb
        )
        modelContext.insert(mlbVisit)
        try modelContext.save()

        var progress = try await engine.getProgress()
        var tripleThreat = progress.first { $0.definition.id == "journey_triple_threat" }
        XCTAssertEqual(tripleThreat!.currentProgress, 1, "Should have 1 league after MLB visit")
        XCTAssertFalse(tripleThreat!.isEarned, "Triple threat needs 3 leagues")

        // Visit NBA stadium - still not enough
        let nbaVisit = StadiumVisit(
            stadiumId: msgId,
            stadiumNameAtVisit: "MSG",
            visitDate: Date(),
            sport: .nba
        )
        modelContext.insert(nbaVisit)
        try modelContext.save()

        progress = try await engine.getProgress()
        tripleThreat = progress.first { $0.definition.id == "journey_triple_threat" }
        XCTAssertEqual(tripleThreat!.currentProgress, 2, "Should have 2 leagues")
        XCTAssertFalse(tripleThreat!.isEarned, "Triple threat needs 3 leagues")

        // Visit NHL stadium - now earned!
        let nhlVisit = StadiumVisit(
            stadiumId: tdGardenId,
            stadiumNameAtVisit: "TD Garden",
            visitDate: Date(),
            sport: .nhl
        )
        modelContext.insert(nhlVisit)
        try modelContext.save()

        progress = try await engine.getProgress()
        tripleThreat = progress.first { $0.definition.id == "journey_triple_threat" }
        XCTAssertEqual(tripleThreat!.currentProgress, 3, "Should have 3 leagues")
        XCTAssertTrue(tripleThreat!.isEarned, "Triple threat should be earned with 3 leagues")
    }
}
