//
//  DynamicSportTests.swift
//  SportsTimeTests
//
//  TDD specification tests for DynamicSport model.
//

import Testing
import Foundation
import SwiftUI
@testable import SportsTime

@Suite("DynamicSport")
struct DynamicSportTests {

    // MARK: - Test Data

    private func makeDynamicSport(
        id: String = "xfl",
        abbreviation: String = "XFL",
        displayName: String = "XFL Football",
        iconName: String = "football.fill",
        colorHex: String = "#FF0000",
        seasonStart: Int = 2,
        seasonEnd: Int = 5
    ) -> DynamicSport {
        DynamicSport(
            id: id,
            abbreviation: abbreviation,
            displayName: displayName,
            iconName: iconName,
            colorHex: colorHex,
            seasonStartMonth: seasonStart,
            seasonEndMonth: seasonEnd
        )
    }

    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: AnySport Conformance

    @Test("sportId: returns id")
    func sportId_returnsId() {
        let sport = makeDynamicSport(id: "test_sport")

        #expect(sport.sportId == "test_sport")
    }

    @Test("displayName: returns displayName property")
    func displayName_returnsProperty() {
        let sport = makeDynamicSport(displayName: "Test Sport Name")

        #expect(sport.displayName == "Test Sport Name")
    }

    @Test("iconName: returns iconName property")
    func iconName_returnsProperty() {
        let sport = makeDynamicSport(iconName: "custom.icon")

        #expect(sport.iconName == "custom.icon")
    }

    @Test("seasonMonths: returns (seasonStartMonth, seasonEndMonth)")
    func seasonMonths_returnsTuple() {
        let sport = makeDynamicSport(seasonStart: 3, seasonEnd: 10)

        let (start, end) = sport.seasonMonths
        #expect(start == 3)
        #expect(end == 10)
    }

    @Test("color: converts colorHex to Color")
    func color_convertsHex() {
        let sport = makeDynamicSport(colorHex: "#FF0000")

        // We can't directly compare Colors, but we can verify it doesn't crash
        // and returns some color value
        let _ = sport.color
    }

    // MARK: - Specification Tests: isInSeason (via AnySport)

    @Test("isInSeason: uses default AnySport implementation for normal season")
    func isInSeason_normalSeason() {
        // Season: February to May (normal range)
        let sport = makeDynamicSport(seasonStart: 2, seasonEnd: 5)

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

    @Test("isInSeason: uses default AnySport implementation for wrap-around season")
    func isInSeason_wrapAroundSeason() {
        // Season: October to April (wraps around)
        let sport = makeDynamicSport(seasonStart: 10, seasonEnd: 4)

        #expect(sport.isInSeason(for: date(month: 10)) == true)
        #expect(sport.isInSeason(for: date(month: 12)) == true)
        #expect(sport.isInSeason(for: date(month: 1)) == true)
        #expect(sport.isInSeason(for: date(month: 4)) == true)
        #expect(sport.isInSeason(for: date(month: 6)) == false)
        #expect(sport.isInSeason(for: date(month: 8)) == false)
    }

    // MARK: - Specification Tests: Identifiable Conformance

    @Test("id: returns id property")
    func id_returnsIdProperty() {
        let sport = makeDynamicSport(id: "unique_id")

        #expect(sport.id == "unique_id")
    }

    // MARK: - Specification Tests: Hashable Conformance

    @Test("hashable: same values are equal")
    func hashable_equality() {
        let sport1 = makeDynamicSport(id: "test")
        let sport2 = makeDynamicSport(id: "test")

        #expect(sport1 == sport2)
    }

    @Test("hashable: different ids are not equal")
    func hashable_inequality() {
        let sport1 = makeDynamicSport(id: "test1")
        let sport2 = makeDynamicSport(id: "test2")

        #expect(sport1 != sport2)
    }

    // MARK: - Specification Tests: Codable Conformance

    @Test("codable: encodes and decodes correctly")
    func codable_roundTrip() throws {
        let sport = makeDynamicSport(
            id: "test_sport",
            abbreviation: "TST",
            displayName: "Test Sport",
            iconName: "star.fill",
            colorHex: "#00FF00",
            seasonStart: 4,
            seasonEnd: 9
        )

        let encoded = try JSONEncoder().encode(sport)
        let decoded = try JSONDecoder().decode(DynamicSport.self, from: encoded)

        #expect(decoded.id == sport.id)
        #expect(decoded.abbreviation == sport.abbreviation)
        #expect(decoded.displayName == sport.displayName)
        #expect(decoded.iconName == sport.iconName)
        #expect(decoded.colorHex == sport.colorHex)
        #expect(decoded.seasonStartMonth == sport.seasonStartMonth)
        #expect(decoded.seasonEndMonth == sport.seasonEndMonth)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: sportId == id")
    func invariant_sportIdEqualsId() {
        let sport = makeDynamicSport(id: "any_id")

        #expect(sport.sportId == sport.id)
    }

    @Test("Invariant: seasonMonths matches individual properties")
    func invariant_seasonMonthsMatchesProperties() {
        let sport = makeDynamicSport(seasonStart: 5, seasonEnd: 11)

        let (start, end) = sport.seasonMonths
        #expect(start == sport.seasonStartMonth)
        #expect(end == sport.seasonEndMonth)
    }
}
