//
//  StadiumProximityMatcherTests.swift
//  SportsTimeTests
//
//  TDD specification tests for StadiumProximityMatcher and related types.
//

import Testing
import Foundation
import CoreLocation
@testable import SportsTime

// MARK: - MatchConfidence Tests

@Suite("MatchConfidence")
struct MatchConfidenceTests {

    // MARK: - Specification Tests: description

    @Test("description: high has description")
    func description_high() {
        #expect(!MatchConfidence.high.description.isEmpty)
    }

    @Test("description: medium has description")
    func description_medium() {
        #expect(!MatchConfidence.medium.description.isEmpty)
    }

    @Test("description: low has description")
    func description_low() {
        #expect(!MatchConfidence.low.description.isEmpty)
    }

    @Test("description: none has description")
    func description_none() {
        #expect(!MatchConfidence.none.description.isEmpty)
    }

    // MARK: - Specification Tests: shouldAutoSelect

    @Test("shouldAutoSelect: true for high confidence")
    func shouldAutoSelect_high() {
        #expect(MatchConfidence.high.shouldAutoSelect == true)
    }

    @Test("shouldAutoSelect: false for medium confidence")
    func shouldAutoSelect_medium() {
        #expect(MatchConfidence.medium.shouldAutoSelect == false)
    }

    @Test("shouldAutoSelect: false for low confidence")
    func shouldAutoSelect_low() {
        #expect(MatchConfidence.low.shouldAutoSelect == false)
    }

    @Test("shouldAutoSelect: false for none confidence")
    func shouldAutoSelect_none() {
        #expect(MatchConfidence.none.shouldAutoSelect == false)
    }

    // MARK: - Specification Tests: Comparable

    @Test("Comparable: high > medium > low > none")
    func comparable_ordering() {
        #expect(MatchConfidence.high > MatchConfidence.medium)
        #expect(MatchConfidence.medium > MatchConfidence.low)
        #expect(MatchConfidence.low > MatchConfidence.none)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: all cases have non-empty description")
    func invariant_allHaveDescription() {
        let cases: [MatchConfidence] = [.high, .medium, .low, .none]
        for confidence in cases {
            #expect(!confidence.description.isEmpty)
        }
    }
}

// MARK: - TemporalConfidence Tests

@Suite("TemporalConfidence")
struct TemporalConfidenceTests {

    // MARK: - Specification Tests: description

    @Test("description: exactDay has description")
    func description_exactDay() {
        #expect(!TemporalConfidence.exactDay.description.isEmpty)
    }

    @Test("description: adjacentDay has description")
    func description_adjacentDay() {
        #expect(!TemporalConfidence.adjacentDay.description.isEmpty)
    }

    @Test("description: outOfRange has description")
    func description_outOfRange() {
        #expect(!TemporalConfidence.outOfRange.description.isEmpty)
    }

    // MARK: - Specification Tests: Comparable

    @Test("Comparable: exactDay > adjacentDay > outOfRange")
    func comparable_ordering() {
        #expect(TemporalConfidence.exactDay > TemporalConfidence.adjacentDay)
        #expect(TemporalConfidence.adjacentDay > TemporalConfidence.outOfRange)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: all cases have non-empty description")
    func invariant_allHaveDescription() {
        let cases: [TemporalConfidence] = [.exactDay, .adjacentDay, .outOfRange]
        for temporal in cases {
            #expect(!temporal.description.isEmpty)
        }
    }
}

// MARK: - CombinedConfidence Tests

@Suite("CombinedConfidence")
struct CombinedConfidenceTests {

    // MARK: - Specification Tests: combine

    @Test("combine: high + exactDay = autoSelect")
    func combine_highExactDay() {
        let result = CombinedConfidence.combine(spatial: .high, temporal: .exactDay)
        #expect(result == .autoSelect)
    }

    @Test("combine: high + adjacentDay = userConfirm")
    func combine_highAdjacentDay() {
        let result = CombinedConfidence.combine(spatial: .high, temporal: .adjacentDay)
        #expect(result == .userConfirm)
    }

    @Test("combine: medium + exactDay = userConfirm")
    func combine_mediumExactDay() {
        let result = CombinedConfidence.combine(spatial: .medium, temporal: .exactDay)
        #expect(result == .userConfirm)
    }

    @Test("combine: medium + adjacentDay = userConfirm")
    func combine_mediumAdjacentDay() {
        let result = CombinedConfidence.combine(spatial: .medium, temporal: .adjacentDay)
        #expect(result == .userConfirm)
    }

    @Test("combine: low spatial = manualOnly regardless of temporal")
    func combine_lowSpatial() {
        #expect(CombinedConfidence.combine(spatial: .low, temporal: .exactDay) == .manualOnly)
        #expect(CombinedConfidence.combine(spatial: .low, temporal: .adjacentDay) == .manualOnly)
        #expect(CombinedConfidence.combine(spatial: .low, temporal: .outOfRange) == .manualOnly)
    }

    @Test("combine: none spatial = manualOnly regardless of temporal")
    func combine_noneSpatial() {
        #expect(CombinedConfidence.combine(spatial: .none, temporal: .exactDay) == .manualOnly)
        #expect(CombinedConfidence.combine(spatial: .none, temporal: .adjacentDay) == .manualOnly)
        #expect(CombinedConfidence.combine(spatial: .none, temporal: .outOfRange) == .manualOnly)
    }

    @Test("combine: outOfRange temporal with high/medium spatial = manualOnly")
    func combine_outOfRangeTemporal() {
        #expect(CombinedConfidence.combine(spatial: .high, temporal: .outOfRange) == .manualOnly)
        #expect(CombinedConfidence.combine(spatial: .medium, temporal: .outOfRange) == .manualOnly)
    }

    // MARK: - Specification Tests: description

    @Test("description: autoSelect has description")
    func description_autoSelect() {
        #expect(!CombinedConfidence.autoSelect.description.isEmpty)
    }

    @Test("description: userConfirm has description")
    func description_userConfirm() {
        #expect(!CombinedConfidence.userConfirm.description.isEmpty)
    }

    @Test("description: manualOnly has description")
    func description_manualOnly() {
        #expect(!CombinedConfidence.manualOnly.description.isEmpty)
    }

    // MARK: - Specification Tests: Comparable

    @Test("Comparable: autoSelect > userConfirm > manualOnly")
    func comparable_ordering() {
        #expect(CombinedConfidence.autoSelect > CombinedConfidence.userConfirm)
        #expect(CombinedConfidence.userConfirm > CombinedConfidence.manualOnly)
    }
}

// MARK: - StadiumMatch Tests

@Suite("StadiumMatch")
struct StadiumMatchTests {

    private func makeStadium() -> Stadium {
        Stadium(
            id: "stadium_1",
            name: "Test Stadium",
            city: "Test City",
            state: "TS",
            latitude: 40.7580,
            longitude: -73.9855,
            capacity: 40000,
            sport: .mlb
        )
    }

    // MARK: - Specification Tests: confidence

    @Test("confidence: high for distance < 500m")
    func confidence_high() {
        let match = StadiumMatch(stadium: makeStadium(), distance: 300)
        #expect(match.confidence == .high)
    }

    @Test("confidence: medium for distance 500m - 2km")
    func confidence_medium() {
        let match = StadiumMatch(stadium: makeStadium(), distance: 1000)
        #expect(match.confidence == .medium)
    }

    @Test("confidence: low for distance 2km - 5km")
    func confidence_low() {
        let match = StadiumMatch(stadium: makeStadium(), distance: 3000)
        #expect(match.confidence == .low)
    }

    @Test("confidence: none for distance > 5km")
    func confidence_none() {
        let match = StadiumMatch(stadium: makeStadium(), distance: 6000)
        #expect(match.confidence == .none)
    }

    // MARK: - Specification Tests: formattedDistance

    @Test("formattedDistance: meters for < 1km")
    func formattedDistance_meters() {
        let match = StadiumMatch(stadium: makeStadium(), distance: 500)
        #expect(match.formattedDistance.contains("m"))
        #expect(!match.formattedDistance.contains("km"))
    }

    @Test("formattedDistance: kilometers for >= 1km")
    func formattedDistance_kilometers() {
        let match = StadiumMatch(stadium: makeStadium(), distance: 2500)
        #expect(match.formattedDistance.contains("km"))
    }

    // MARK: - Specification Tests: Identifiable

    @Test("id: matches stadium id")
    func id_matchesStadiumId() {
        let stadium = makeStadium()
        let match = StadiumMatch(stadium: stadium, distance: 100)
        #expect(match.id == stadium.id)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: confidence boundaries")
    func invariant_confidenceBoundaries() {
        let stadium = makeStadium()

        // Boundary at 500m
        #expect(StadiumMatch(stadium: stadium, distance: 499).confidence == .high)
        #expect(StadiumMatch(stadium: stadium, distance: 500).confidence == .medium)

        // Boundary at 2000m
        #expect(StadiumMatch(stadium: stadium, distance: 1999).confidence == .medium)
        #expect(StadiumMatch(stadium: stadium, distance: 2000).confidence == .low)

        // Boundary at 5000m
        #expect(StadiumMatch(stadium: stadium, distance: 4999).confidence == .low)
        #expect(StadiumMatch(stadium: stadium, distance: 5000).confidence == .none)
    }
}

// MARK: - PhotoMatchConfidence Composition Tests

@Suite("PhotoMatchConfidence Composition")
struct PhotoMatchConfidenceCompositionTests {

    @Test("combined: derived from spatial and temporal")
    func combined_derived() {
        let confidence = PhotoMatchConfidence(spatial: .high, temporal: .exactDay)
        #expect(confidence.combined == .autoSelect)
        #expect(confidence.spatial == .high)
        #expect(confidence.temporal == .exactDay)
    }

    @Test("combined: matches CombinedConfidence.combine result")
    func combined_matchesCombine() {
        let spatials: [MatchConfidence] = [.high, .medium, .low, .none]
        let temporals: [TemporalConfidence] = [.exactDay, .adjacentDay, .outOfRange]

        for spatial in spatials {
            for temporal in temporals {
                let confidence = PhotoMatchConfidence(spatial: spatial, temporal: temporal)
                let expected = CombinedConfidence.combine(spatial: spatial, temporal: temporal)
                #expect(confidence.combined == expected)
            }
        }
    }
}

// MARK: - ProximityConstants Tests

@Suite("ProximityConstants")
struct ProximityConstantsTests {

    @Test("highConfidenceRadius: 500m")
    func highConfidenceRadius() {
        #expect(ProximityConstants.highConfidenceRadius == 500)
    }

    @Test("mediumConfidenceRadius: 2km")
    func mediumConfidenceRadius() {
        #expect(ProximityConstants.mediumConfidenceRadius == 2000)
    }

    @Test("searchRadius: 5km")
    func searchRadius() {
        #expect(ProximityConstants.searchRadius == 5000)
    }

    @Test("dateToleranceDays: 1")
    func dateToleranceDays() {
        #expect(ProximityConstants.dateToleranceDays == 1)
    }
}
