//
//  RouteDescriptionGeneratorTests.swift
//  SportsTimeTests
//
//  TDD specification tests for RouteDescriptionGenerator types.
//

import Testing
import Foundation
import CoreLocation
@testable import SportsTime

// MARK: - RouteDescriptionInput Tests

@Suite("RouteDescriptionInput")
struct RouteDescriptionInputTests {

    // MARK: - Test Data

    private let nycCoord = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855)
    private let bostonCoord = CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)

    private func makeOption(
        stops: [ItineraryStop] = [],
        totalDrivingHours: Double = 8.5,
        totalDistanceMiles: Double = 500
    ) -> ItineraryOption {
        ItineraryOption(
            rank: 1,
            stops: stops,
            travelSegments: [],
            totalDrivingHours: totalDrivingHours,
            totalDistanceMiles: totalDistanceMiles,
            geographicRationale: "Test rationale"
        )
    }

    private func makeStop(city: String, games: [String] = []) -> ItineraryStop {
        ItineraryStop(
            city: city,
            state: "XX",
            coordinate: nycCoord,
            games: games,
            arrivalDate: Date(),
            departureDate: Date().addingTimeInterval(86400),
            location: LocationInput(name: city, coordinate: nycCoord),
            firstGameStart: nil
        )
    }

    private func makeRichGame(id: String, sport: Sport = .mlb) -> RichGame {
        let game = Game(
            id: id,
            homeTeamId: "team1",
            awayTeamId: "team2",
            stadiumId: "stadium1",
            dateTime: Date(),
            sport: sport,
            season: "2026",
            isPlayoff: false
        )
        let team = Team(
            id: "team1",
            name: "Test Team",
            abbreviation: "TST",
            sport: sport,
            city: "Test City",
            stadiumId: "stadium1"
        )
        let stadium = Stadium(
            id: "stadium1",
            name: "Test Stadium",
            city: "Test City",
            state: "XX",
            latitude: nycCoord.latitude,
            longitude: nycCoord.longitude,
            capacity: 40000,
            sport: sport
        )
        return RichGame(game: game, homeTeam: team, awayTeam: team, stadium: stadium)
    }

    // MARK: - Specification Tests: init(from:games:)

    @Test("init: extracts cities from option stops")
    func init_extractsCities() {
        let stops = [
            makeStop(city: "New York"),
            makeStop(city: "Boston"),
            makeStop(city: "Philadelphia")
        ]
        let option = makeOption(stops: stops)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.cities == ["New York", "Boston", "Philadelphia"])
    }

    @Test("init: deduplicates cities preserving order")
    func init_deduplicatesCities() {
        let stops = [
            makeStop(city: "New York"),
            makeStop(city: "Boston"),
            makeStop(city: "New York")  // Duplicate
        ]
        let option = makeOption(stops: stops)
        let input = RouteDescriptionInput(from: option, games: [:])

        // NSOrderedSet preserves first occurrence order and removes duplicates
        #expect(input.cities == ["New York", "Boston"])
    }

    @Test("init: extracts sports from games")
    func init_extractsSports() {
        let stops = [makeStop(city: "New York", games: ["game1", "game2"])]
        let option = makeOption(stops: stops)
        let games = [
            "game1": makeRichGame(id: "game1", sport: .mlb),
            "game2": makeRichGame(id: "game2", sport: .nba)
        ]
        let input = RouteDescriptionInput(from: option, games: games)

        #expect(input.sports.count == 2)
        #expect(input.sports.contains("MLB"))  // rawValue is uppercase
        #expect(input.sports.contains("NBA"))  // rawValue is uppercase
    }

    @Test("init: computes totalGames from option")
    func init_computesTotalGames() {
        let stops = [
            makeStop(city: "New York", games: ["g1", "g2"]),
            makeStop(city: "Boston", games: ["g3"])
        ]
        let option = makeOption(stops: stops)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.totalGames == 3)
    }

    @Test("init: copies totalMiles from option")
    func init_copiesTotalMiles() {
        let option = makeOption(totalDistanceMiles: 1234.5)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.totalMiles == 1234.5)
    }

    @Test("init: copies totalDrivingHours from option")
    func init_copiesTotalDrivingHours() {
        let option = makeOption(totalDrivingHours: 15.75)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.totalDrivingHours == 15.75)
    }

    @Test("init: copies id from option")
    func init_copiesId() {
        let option = makeOption()
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.id == option.id)
    }

    // MARK: - Edge Cases

    @Test("init: handles empty stops")
    func init_emptyStops() {
        let option = makeOption(stops: [])
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.cities.isEmpty)
        #expect(input.totalGames == 0)
    }

    @Test("init: handles empty games dictionary")
    func init_emptyGames() {
        let stops = [makeStop(city: "NYC", games: ["g1"])]
        let option = makeOption(stops: stops)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.sports.isEmpty)
    }

    @Test("init: handles zero distance and hours")
    func init_zeroValues() {
        let option = makeOption(totalDrivingHours: 0, totalDistanceMiles: 0)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.totalMiles == 0)
        #expect(input.totalDrivingHours == 0)
    }

    @Test("init: handles single city")
    func init_singleCity() {
        let stops = [makeStop(city: "Only City")]
        let option = makeOption(stops: stops)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.cities.count == 1)
        #expect(input.cities.first == "Only City")
    }

    // MARK: - Invariant Tests

    /// - Invariant: cities preserves stop order
    @Test("Invariant: cities preserves stop order")
    func invariant_citiesPreservesOrder() {
        let stops = [
            makeStop(city: "First"),
            makeStop(city: "Second"),
            makeStop(city: "Third")
        ]
        let option = makeOption(stops: stops)
        let input = RouteDescriptionInput(from: option, games: [:])

        #expect(input.cities == ["First", "Second", "Third"])
    }
}

// MARK: - RouteDescription Tests

@Suite("RouteDescription")
struct RouteDescriptionTests {

    // MARK: - Specification Tests

    /// - Expected Behavior: RouteDescription stores description string
    @Test("RouteDescription: stores description")
    func routeDescription_storesDescription() {
        let desc = RouteDescription(description: "An exciting road trip!")
        #expect(desc.description == "An exciting road trip!")
    }

    @Test("RouteDescription: handles empty description")
    func routeDescription_emptyDescription() {
        let desc = RouteDescription(description: "")
        #expect(desc.description == "")
    }

    @Test("RouteDescription: handles long description")
    func routeDescription_longDescription() {
        let longText = String(repeating: "A", count: 1000)
        let desc = RouteDescription(description: longText)
        #expect(desc.description == longText)
    }

    // MARK: - Invariant Tests

    /// - Invariant: description is never nil (non-optional)
    @Test("Invariant: description is non-optional")
    func invariant_descriptionNonOptional() {
        let desc = RouteDescription(description: "Test")
        // Just accessing .description should always work
        let _ = desc.description
        #expect(Bool(true)) // If we got here, description is non-optional
    }
}
