//
//  AchievementDefinitionsTests.swift
//  SportsTimeTests
//
//  TDD specification tests for AchievementRegistry and related models.
//

import Testing
import SwiftUI
@testable import SportsTime

@Suite("AchievementCategory")
struct AchievementCategoryTests {

    @Test("Property: all cases have displayName")
    func property_allHaveDisplayName() {
        for category in AchievementCategory.allCases {
            #expect(!category.displayName.isEmpty)
        }
    }

    @Test("displayName: count returns 'Milestones'")
    func displayName_count() {
        #expect(AchievementCategory.count.displayName == "Milestones")
    }

    @Test("displayName: division returns 'Divisions'")
    func displayName_division() {
        #expect(AchievementCategory.division.displayName == "Divisions")
    }
}

// MARK: - AchievementDefinition Tests

@Suite("AchievementDefinition")
struct AchievementDefinitionTests {

    @Test("equality: based on id only")
    func equality_basedOnId() {
        let def1 = AchievementDefinition(
            id: "test_achievement",
            name: "Name A",
            description: "Description A",
            category: .count,
            iconName: "icon.a",
            iconColor: .blue,
            requirement: .visitCount(5)
        )

        let def2 = AchievementDefinition(
            id: "test_achievement",
            name: "Different Name",
            description: "Different Description",
            category: .division,
            iconName: "icon.b",
            iconColor: .red,
            requirement: .visitCount(10)
        )

        #expect(def1 == def2, "Achievements with same id should be equal")
    }

    @Test("inequality: different ids")
    func inequality_differentIds() {
        let def1 = AchievementDefinition(
            id: "achievement_1",
            name: "Same Name",
            description: "Same Description",
            category: .count,
            iconName: "icon",
            iconColor: .blue,
            requirement: .visitCount(5)
        )

        let def2 = AchievementDefinition(
            id: "achievement_2",
            name: "Same Name",
            description: "Same Description",
            category: .count,
            iconName: "icon",
            iconColor: .blue,
            requirement: .visitCount(5)
        )

        #expect(def1 != def2, "Achievements with different ids should not be equal")
    }

    @Test("Property: divisionId can be set")
    func property_divisionId() {
        let def = AchievementDefinition(
            id: "test_div_ach",
            name: "Division Achievement",
            description: "Complete a division",
            category: .division,
            sport: .mlb,
            iconName: "baseball.fill",
            iconColor: .red,
            requirement: .completeDivision("mlb_al_east"),
            divisionId: "mlb_al_east"
        )

        #expect(def.divisionId == "mlb_al_east")
    }

    @Test("Property: conferenceId can be set")
    func property_conferenceId() {
        let def = AchievementDefinition(
            id: "test_conf_ach",
            name: "Conference Achievement",
            description: "Complete a conference",
            category: .conference,
            sport: .mlb,
            iconName: "baseball.fill",
            iconColor: .red,
            requirement: .completeConference("mlb_al"),
            conferenceId: "mlb_al"
        )

        #expect(def.conferenceId == "mlb_al")
    }
}

// MARK: - AchievementRegistry Tests

@Suite("AchievementRegistry")
struct AchievementRegistryTests {

    // MARK: - Specification Tests: all

    @Test("all: is sorted by sortOrder ascending")
    func all_sortedBySortOrder() {
        let achievements = AchievementRegistry.all

        for i in 1..<achievements.count {
            #expect(
                achievements[i].sortOrder >= achievements[i - 1].sortOrder,
                "Achievement at index \(i) should have sortOrder >= previous"
            )
        }
    }

    @Test("all: contains count achievements")
    func all_containsCountAchievements() {
        let countAchievements = AchievementRegistry.all.filter { $0.category == .count }

        #expect(countAchievements.count >= 5, "Should have multiple count achievements")
    }

    @Test("all: contains division achievements")
    func all_containsDivisionAchievements() {
        let divisionAchievements = AchievementRegistry.all.filter { $0.category == .division }

        #expect(divisionAchievements.count >= 16, "Should have division achievements for MLB, NBA, NHL")
    }

    // MARK: - Specification Tests: achievement(byId:)

    @Test("achievement(byId:): finds existing achievement")
    func achievementById_found() {
        let achievement = AchievementRegistry.achievement(byId: "first_visit")

        #expect(achievement != nil)
        #expect(achievement?.name == "First Pitch")
    }

    @Test("achievement(byId:): returns nil for unknown ID")
    func achievementById_notFound() {
        let achievement = AchievementRegistry.achievement(byId: "nonexistent_achievement")

        #expect(achievement == nil)
    }

    // MARK: - Specification Tests: achievements(forCategory:)

    @Test("achievements(forCategory:): filters by exact category")
    func achievementsForCategory_filters() {
        let countAchievements = AchievementRegistry.achievements(forCategory: .count)

        for achievement in countAchievements {
            #expect(achievement.category == .count)
        }
    }

    @Test("achievements(forCategory:): returns all division achievements")
    func achievementsForCategory_division() {
        let divisionAchievements = AchievementRegistry.achievements(forCategory: .division)

        #expect(!divisionAchievements.isEmpty)
        for achievement in divisionAchievements {
            #expect(achievement.category == .division)
        }
    }

    // MARK: - Specification Tests: achievements(forSport:)

    @Test("achievements(forSport:): includes sport-specific achievements")
    func achievementsForSport_includesSportSpecific() {
        let mlbAchievements = AchievementRegistry.achievements(forSport: .mlb)

        let sportSpecific = mlbAchievements.filter { $0.sport == .mlb }
        #expect(!sportSpecific.isEmpty, "Should include MLB-specific achievements")
    }

    @Test("achievements(forSport:): includes cross-sport achievements")
    func achievementsForSport_includesCrossSport() {
        let mlbAchievements = AchievementRegistry.achievements(forSport: .mlb)

        let crossSport = mlbAchievements.filter { $0.sport == nil }
        #expect(!crossSport.isEmpty, "Should include cross-sport achievements (sport == nil)")
    }

    // MARK: - Specification Tests: divisionAchievements(forSport:)

    @Test("divisionAchievements(forSport:): filters to division category AND sport")
    func divisionAchievementsForSport_filters() {
        let mlbDivisionAchievements = AchievementRegistry.divisionAchievements(forSport: .mlb)

        #expect(mlbDivisionAchievements.count == 6, "MLB has 6 divisions")

        for achievement in mlbDivisionAchievements {
            #expect(achievement.category == .division)
            #expect(achievement.sport == .mlb)
        }
    }

    @Test("divisionAchievements(forSport:): NBA has 6 divisions")
    func divisionAchievementsForSport_nba() {
        let nbaDivisionAchievements = AchievementRegistry.divisionAchievements(forSport: .nba)

        #expect(nbaDivisionAchievements.count == 6)
    }

    @Test("divisionAchievements(forSport:): NHL has 4 divisions")
    func divisionAchievementsForSport_nhl() {
        let nhlDivisionAchievements = AchievementRegistry.divisionAchievements(forSport: .nhl)

        #expect(nhlDivisionAchievements.count == 4)
    }

    // MARK: - Specification Tests: conferenceAchievements(forSport:)

    @Test("conferenceAchievements(forSport:): filters to conference category AND sport")
    func conferenceAchievementsForSport_filters() {
        let mlbConferenceAchievements = AchievementRegistry.conferenceAchievements(forSport: .mlb)

        #expect(mlbConferenceAchievements.count == 2, "MLB has 2 leagues (AL, NL)")

        for achievement in mlbConferenceAchievements {
            #expect(achievement.category == .conference)
            #expect(achievement.sport == .mlb)
        }
    }

    // MARK: - Invariant Tests

    @Test("Invariant: all achievement IDs are unique")
    func invariant_uniqueIds() {
        let ids = AchievementRegistry.all.map { $0.id }
        let uniqueIds = Set(ids)

        #expect(ids.count == uniqueIds.count, "All achievement IDs should be unique")
    }

    @Test("Invariant: division achievements have non-nil divisionId")
    func invariant_divisionAchievementsHaveDivisionId() {
        let divisionAchievements = AchievementRegistry.achievements(forCategory: .division)

        for achievement in divisionAchievements {
            #expect(
                achievement.divisionId != nil,
                "Division achievement \(achievement.id) should have divisionId"
            )
        }
    }

    @Test("Invariant: conference achievements have non-nil conferenceId")
    func invariant_conferenceAchievementsHaveConferenceId() {
        let conferenceAchievements = AchievementRegistry.achievements(forCategory: .conference)

        for achievement in conferenceAchievements {
            #expect(
                achievement.conferenceId != nil,
                "Conference achievement \(achievement.id) should have conferenceId"
            )
        }
    }

    @Test("Invariant: all achievements have non-empty name")
    func invariant_allHaveNames() {
        for achievement in AchievementRegistry.all {
            #expect(!achievement.name.isEmpty, "Achievement \(achievement.id) should have a name")
        }
    }

    @Test("Invariant: all achievements have non-empty description")
    func invariant_allHaveDescriptions() {
        for achievement in AchievementRegistry.all {
            #expect(!achievement.description.isEmpty, "Achievement \(achievement.id) should have a description")
        }
    }

    @Test("Invariant: all achievements have non-empty iconName")
    func invariant_allHaveIconNames() {
        for achievement in AchievementRegistry.all {
            #expect(!achievement.iconName.isEmpty, "Achievement \(achievement.id) should have an iconName")
        }
    }
}

// MARK: - AchievementRequirement Tests

@Suite("AchievementRequirement")
struct AchievementRequirementTests {

    @Test("Property: visitCount requirement stores count")
    func visitCount_storesValue() {
        let requirement = AchievementRequirement.visitCount(10)

        if case .visitCount(let count) = requirement {
            #expect(count == 10)
        } else {
            Issue.record("Should be visitCount case")
        }
    }

    @Test("Property: completeDivision requirement stores division ID")
    func completeDivision_storesId() {
        let requirement = AchievementRequirement.completeDivision("mlb_al_east")

        if case .completeDivision(let divisionId) = requirement {
            #expect(divisionId == "mlb_al_east")
        } else {
            Issue.record("Should be completeDivision case")
        }
    }

    @Test("Property: visitsInDays requirement stores both values")
    func visitsInDays_storesValues() {
        let requirement = AchievementRequirement.visitsInDays(5, days: 7)

        if case .visitsInDays(let visits, let days) = requirement {
            #expect(visits == 5)
            #expect(days == 7)
        } else {
            Issue.record("Should be visitsInDays case")
        }
    }

    @Test("hashable: same requirements are equal")
    func hashable_equality() {
        let req1 = AchievementRequirement.visitCount(10)
        let req2 = AchievementRequirement.visitCount(10)

        #expect(req1 == req2)
    }

    @Test("hashable: different requirements are not equal")
    func hashable_inequality() {
        let req1 = AchievementRequirement.visitCount(10)
        let req2 = AchievementRequirement.visitCount(20)

        #expect(req1 != req2)
    }
}
