//
//  AnySportTests.swift
//  SportsTimeTests
//
//  TDD specification tests for AnySport protocol default implementations.
//

import Testing
import Foundation
import SwiftUI
@testable import SportsTime

// MARK: - Mock AnySport for Testing

/// A mock type that conforms to AnySport for testing the default implementation
private struct MockSport: AnySport, Hashable {
    let sportId: String
    let displayName: String
    let iconName: String
    let color: SwiftUI.Color
    let seasonMonths: (start: Int, end: Int)

    var id: String { sportId }

    init(
        sportId: String = "mock",
        displayName: String = "Mock Sport",
        iconName: String = "sportscourt",
        color: SwiftUI.Color = .blue,
        seasonStart: Int,
        seasonEnd: Int
    ) {
        self.sportId = sportId
        self.displayName = displayName
        self.iconName = iconName
        self.color = color
        self.seasonMonths = (seasonStart, seasonEnd)
    }

    static func == (lhs: MockSport, rhs: MockSport) -> Bool {
        lhs.sportId == rhs.sportId
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(sportId)
    }
}

@Suite("AnySport Protocol")
struct AnySportTests {

    // MARK: - Test Data

    private var calendar: Calendar { Calendar.current }

    private func date(month: Int) -> Date {
        calendar.date(from: DateComponents(year: 2026, month: month, day: 15))!
    }

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

    @Test("isInSeason: normal season (start <= end), month in range returns true")
    func isInSeason_normalSeason_inRange() {
        // Season: April (4) to October (10)
        let sport = MockSport(seasonStart: 4, seasonEnd: 10)

        #expect(sport.isInSeason(for: date(month: 4)) == true) // April
        #expect(sport.isInSeason(for: date(month: 7)) == true) // July
        #expect(sport.isInSeason(for: date(month: 10)) == true) // October
    }

    @Test("isInSeason: normal season, month outside range returns false")
    func isInSeason_normalSeason_outOfRange() {
        // Season: April (4) to October (10)
        let sport = MockSport(seasonStart: 4, seasonEnd: 10)

        #expect(sport.isInSeason(for: date(month: 1)) == false) // January
        #expect(sport.isInSeason(for: date(month: 3)) == false) // March
        #expect(sport.isInSeason(for: date(month: 11)) == false) // November
        #expect(sport.isInSeason(for: date(month: 12)) == false) // December
    }

    @Test("isInSeason: normal season boundary - start month is in season")
    func isInSeason_normalSeason_startBoundary() {
        let sport = MockSport(seasonStart: 3, seasonEnd: 10)

        #expect(sport.isInSeason(for: date(month: 3)) == true)
        #expect(sport.isInSeason(for: date(month: 2)) == false)
    }

    @Test("isInSeason: normal season boundary - end month is in season")
    func isInSeason_normalSeason_endBoundary() {
        let sport = MockSport(seasonStart: 3, seasonEnd: 10)

        #expect(sport.isInSeason(for: date(month: 10)) == true)
        #expect(sport.isInSeason(for: date(month: 11)) == false)
    }

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

    @Test("isInSeason: wrap-around season (start > end), month >= start returns true")
    func isInSeason_wrapAround_afterStart() {
        // Season: October (10) to June (6) - wraps around year
        let sport = MockSport(seasonStart: 10, seasonEnd: 6)

        #expect(sport.isInSeason(for: date(month: 10)) == true) // October (start)
        #expect(sport.isInSeason(for: date(month: 11)) == true) // November
        #expect(sport.isInSeason(for: date(month: 12)) == true) // December
    }

    @Test("isInSeason: wrap-around season, month <= end returns true")
    func isInSeason_wrapAround_beforeEnd() {
        // Season: October (10) to June (6) - wraps around year
        let sport = MockSport(seasonStart: 10, seasonEnd: 6)

        #expect(sport.isInSeason(for: date(month: 1)) == true) // January
        #expect(sport.isInSeason(for: date(month: 3)) == true) // March
        #expect(sport.isInSeason(for: date(month: 6)) == true) // June (end)
    }

    @Test("isInSeason: wrap-around season, gap months return false")
    func isInSeason_wrapAround_gap() {
        // Season: October (10) to June (6) - gap is July, August, September
        let sport = MockSport(seasonStart: 10, seasonEnd: 6)

        #expect(sport.isInSeason(for: date(month: 7)) == false) // July
        #expect(sport.isInSeason(for: date(month: 8)) == false) // August
        #expect(sport.isInSeason(for: date(month: 9)) == false) // September
    }

    @Test("isInSeason: wrap-around season boundary - start month is in season")
    func isInSeason_wrapAround_startBoundary() {
        let sport = MockSport(seasonStart: 10, seasonEnd: 4)

        #expect(sport.isInSeason(for: date(month: 10)) == true)
        #expect(sport.isInSeason(for: date(month: 9)) == false)
    }

    @Test("isInSeason: wrap-around season boundary - end month is in season")
    func isInSeason_wrapAround_endBoundary() {
        let sport = MockSport(seasonStart: 10, seasonEnd: 4)

        #expect(sport.isInSeason(for: date(month: 4)) == true)
        #expect(sport.isInSeason(for: date(month: 5)) == false)
    }

    // MARK: - Specification Tests: Edge Cases

    @Test("isInSeason: single month season (start == end)")
    func isInSeason_singleMonth() {
        // Season is only March
        let sport = MockSport(seasonStart: 3, seasonEnd: 3)

        #expect(sport.isInSeason(for: date(month: 3)) == true)
        #expect(sport.isInSeason(for: date(month: 2)) == false)
        #expect(sport.isInSeason(for: date(month: 4)) == false)
    }

    @Test("isInSeason: full year season (January to December)")
    func isInSeason_fullYear() {
        let sport = MockSport(seasonStart: 1, seasonEnd: 12)

        for month in 1...12 {
            #expect(sport.isInSeason(for: date(month: month)) == true)
        }
    }

    // MARK: - Invariant Tests

    @Test("Invariant: isInSeason returns true for exactly the months in range")
    func invariant_exactlyMonthsInRange() {
        // Normal season: March to October
        let normalSport = MockSport(seasonStart: 3, seasonEnd: 10)

        let expectedInSeason = Set(3...10)
        for month in 1...12 {
            let expected = expectedInSeason.contains(month)
            #expect(normalSport.isInSeason(for: date(month: month)) == expected)
        }
    }

    @Test("Invariant: wrap-around isInSeason returns true for months >= start OR <= end")
    func invariant_wrapAroundMonths() {
        // Wrap-around season: October to April
        let wrapSport = MockSport(seasonStart: 10, seasonEnd: 4)

        // In season: 10, 11, 12, 1, 2, 3, 4
        let expectedInSeason = Set([10, 11, 12, 1, 2, 3, 4])
        for month in 1...12 {
            let expected = expectedInSeason.contains(month)
            #expect(wrapSport.isInSeason(for: date(month: month)) == expected)
        }
    }
}
