//
//  SportTests.swift
//  SportsTimeTests
//
//  TDD specification tests for Sport enum.
//

import Testing
import Foundation
@testable import SportsTime

@Suite("Sport")
struct SportTests {

    // MARK: - Specification Tests: Season Months

    @Test("MLB season: March (3) to October (10)")
    func mlb_seasonMonths() {
        let (start, end) = Sport.mlb.seasonMonths
        #expect(start == 3)
        #expect(end == 10)
    }

    @Test("NBA season: October (10) to June (6) - wraps around year")
    func nba_seasonMonths() {
        let (start, end) = Sport.nba.seasonMonths
        #expect(start == 10)
        #expect(end == 6)
    }

    @Test("NHL season: October (10) to June (6) - wraps around year")
    func nhl_seasonMonths() {
        let (start, end) = Sport.nhl.seasonMonths
        #expect(start == 10)
        #expect(end == 6)
    }

    @Test("NFL season: September (9) to February (2) - wraps around year")
    func nfl_seasonMonths() {
        let (start, end) = Sport.nfl.seasonMonths
        #expect(start == 9)
        #expect(end == 2)
    }

    @Test("MLS season: February (2) to December (12)")
    func mls_seasonMonths() {
        let (start, end) = Sport.mls.seasonMonths
        #expect(start == 2)
        #expect(end == 12)
    }

    @Test("WNBA season: May (5) to October (10)")
    func wnba_seasonMonths() {
        let (start, end) = Sport.wnba.seasonMonths
        #expect(start == 5)
        #expect(end == 10)
    }

    @Test("NWSL season: March (3) to November (11)")
    func nwsl_seasonMonths() {
        let (start, end) = Sport.nwsl.seasonMonths
        #expect(start == 3)
        #expect(end == 11)
    }

    // MARK: - Specification Tests: isInSeason (Normal Range)

    @Test("MLB: isInSeason returns true for months 3-10")
    func mlb_isInSeason_normalRange() {
        let calendar = Calendar.current

        // In season: March through October
        for month in 3...10 {
            let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
            #expect(Sport.mlb.isInSeason(for: date), "MLB should be in season in month \(month)")
        }

        // Out of season: January, February, November, December
        for month in [1, 2, 11, 12] {
            let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
            #expect(!Sport.mlb.isInSeason(for: date), "MLB should NOT be in season in month \(month)")
        }
    }

    // MARK: - Specification Tests: isInSeason (Wrap-Around)

    @Test("NBA: isInSeason returns true for months 10-12 and 1-6 (wrap-around)")
    func nba_isInSeason_wrapAround() {
        let calendar = Calendar.current

        // In season: October through June (wraps)
        let inSeasonMonths = [10, 11, 12, 1, 2, 3, 4, 5, 6]
        for month in inSeasonMonths {
            let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
            #expect(Sport.nba.isInSeason(for: date), "NBA should be in season in month \(month)")
        }

        // Out of season: July, August, September
        for month in [7, 8, 9] {
            let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
            #expect(!Sport.nba.isInSeason(for: date), "NBA should NOT be in season in month \(month)")
        }
    }

    @Test("NFL: isInSeason returns true for months 9-12 and 1-2 (wrap-around)")
    func nfl_isInSeason_wrapAround() {
        let calendar = Calendar.current

        // In season: September through February (wraps)
        let inSeasonMonths = [9, 10, 11, 12, 1, 2]
        for month in inSeasonMonths {
            let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
            #expect(Sport.nfl.isInSeason(for: date), "NFL should be in season in month \(month)")
        }

        // Out of season: March through August
        for month in 3...8 {
            let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
            #expect(!Sport.nfl.isInSeason(for: date), "NFL should NOT be in season in month \(month)")
        }
    }

    // MARK: - Specification Tests: Boundary Values

    @Test("isInSeason boundary: first and last day of season month")
    func isInSeason_boundaryDays() {
        let calendar = Calendar.current

        // MLB: First day of March (in season)
        let marchFirst = calendar.date(from: DateComponents(year: 2026, month: 3, day: 1))!
        #expect(Sport.mlb.isInSeason(for: marchFirst))

        // MLB: Last day of October (in season)
        let octLast = calendar.date(from: DateComponents(year: 2026, month: 10, day: 31))!
        #expect(Sport.mlb.isInSeason(for: octLast))

        // MLB: First day of November (out of season)
        let novFirst = calendar.date(from: DateComponents(year: 2026, month: 11, day: 1))!
        #expect(!Sport.mlb.isInSeason(for: novFirst))

        // MLB: Last day of February (out of season)
        let febLast = calendar.date(from: DateComponents(year: 2026, month: 2, day: 28))!
        #expect(!Sport.mlb.isInSeason(for: febLast))
    }

    // MARK: - Specification Tests: Supported Sports

    @Test("supported returns all 7 sports")
    func supported_returnsAllSports() {
        let supported = Sport.supported
        #expect(supported.count == 7)
        #expect(supported.contains(.mlb))
        #expect(supported.contains(.nba))
        #expect(supported.contains(.nhl))
        #expect(supported.contains(.nfl))
        #expect(supported.contains(.mls))
        #expect(supported.contains(.wnba))
        #expect(supported.contains(.nwsl))
    }

    @Test("CaseIterable matches supported")
    func caseIterable_matchesSupported() {
        let allCases = Set(Sport.allCases)
        let supported = Set(Sport.supported)
        #expect(allCases == supported)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: seasonMonths values are always 1-12")
    func invariant_seasonMonthsValidRange() {
        for sport in Sport.allCases {
            let (start, end) = sport.seasonMonths
            #expect(start >= 1 && start <= 12, "\(sport) start month must be 1-12")
            #expect(end >= 1 && end <= 12, "\(sport) end month must be 1-12")
        }
    }

    @Test("Invariant: each sport has unique displayName")
    func invariant_uniqueDisplayNames() {
        var displayNames: Set<String> = []
        for sport in Sport.allCases {
            #expect(!displayNames.contains(sport.displayName), "Duplicate displayName: \(sport.displayName)")
            displayNames.insert(sport.displayName)
        }
    }

    // MARK: - Property Tests

    @Test("Property: id equals rawValue")
    func property_idEqualsRawValue() {
        for sport in Sport.allCases {
            #expect(sport.id == sport.rawValue)
        }
    }

    @Test("Property: sportId equals rawValue (AnySport conformance)")
    func property_sportIdEqualsRawValue() {
        for sport in Sport.allCases {
            #expect(sport.sportId == sport.rawValue)
        }
    }
}
