//
//  ConcurrencyTests.swift
//  SportsTimeTests
//
//  Phase 10: Concurrency Tests
//  Documents current thread-safety behavior for future refactoring reference.
//

import Testing
import CoreLocation
@testable import SportsTime

@Suite("Concurrency Tests", .serialized)
struct ConcurrencyTests {

    // MARK: - Test Fixtures

    private let calendar = Calendar.current

    /// Creates a date with specific year/month/day/hour
    private func makeDate(year: Int = 2026, month: Int = 6, day: Int, hour: Int = 19) -> Date {
        var components = DateComponents()
        components.year = year
        components.month = month
        components.day = day
        components.hour = hour
        components.minute = 0
        return calendar.date(from: components)!
    }

    /// Creates a stadium at a known location
    private func makeStadium(
        id: UUID = UUID(),
        city: String,
        lat: Double,
        lon: Double,
        sport: Sport = .mlb
    ) -> Stadium {
        Stadium(
            id: id,
            name: "\(city) Stadium",
            city: city,
            state: "ST",
            latitude: lat,
            longitude: lon,
            capacity: 40000,
            sport: sport
        )
    }

    /// Creates a game at a stadium
    private func makeGame(
        id: UUID = UUID(),
        stadiumId: UUID,
        homeTeamId: UUID = UUID(),
        awayTeamId: UUID = UUID(),
        dateTime: Date,
        sport: Sport = .mlb
    ) -> Game {
        Game(
            id: id,
            homeTeamId: homeTeamId,
            awayTeamId: awayTeamId,
            stadiumId: stadiumId,
            dateTime: dateTime,
            sport: sport,
            season: "2026"
        )
    }

    /// Creates a PlanningRequest for Scenario A (date range only)
    private func makeScenarioARequest(
        startDate: Date,
        endDate: Date,
        games: [Game],
        stadiums: [UUID: Stadium]
    ) -> PlanningRequest {
        let preferences = TripPreferences(
            planningMode: .dateRange,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            numberOfDrivers: 1,
            maxDrivingHoursPerDriver: 8.0,
            allowRepeatCities: true
        )

        return PlanningRequest(
            preferences: preferences,
            availableGames: games,
            teams: [:],
            stadiums: stadiums
        )
    }

    /// Creates a valid test request with nearby cities
    private func makeValidTestRequest(requestIndex: Int) -> PlanningRequest {
        // Use different but nearby city pairs for each request to create variety
        let cityPairs: [(city1: (String, Double, Double), city2: (String, Double, Double))] = [
            (("Chicago", 41.8781, -87.6298), ("Milwaukee", 43.0389, -87.9065)),
            (("New York", 40.7128, -73.9352), ("Philadelphia", 39.9526, -75.1652)),
            (("Boston", 42.3601, -71.0589), ("Providence", 41.8240, -71.4128)),
            (("Los Angeles", 34.0522, -118.2437), ("San Diego", 32.7157, -117.1611)),
            (("Seattle", 47.6062, -122.3321), ("Portland", 45.5152, -122.6784)),
        ]

        let pair = cityPairs[requestIndex % cityPairs.count]

        let stadium1Id = UUID()
        let stadium2Id = UUID()

        let stadium1 = makeStadium(id: stadium1Id, city: pair.city1.0, lat: pair.city1.1, lon: pair.city1.2)
        let stadium2 = makeStadium(id: stadium2Id, city: pair.city2.0, lat: pair.city2.1, lon: pair.city2.2)

        let stadiums = [stadium1Id: stadium1, stadium2Id: stadium2]

        // Games on different days for feasible routing
        let baseDay = 5 + (requestIndex * 2) % 20
        let game1 = makeGame(stadiumId: stadium1Id, dateTime: makeDate(day: baseDay, hour: 19))
        let game2 = makeGame(stadiumId: stadium2Id, dateTime: makeDate(day: baseDay + 2, hour: 19))

        return makeScenarioARequest(
            startDate: makeDate(day: baseDay - 1, hour: 0),
            endDate: makeDate(day: baseDay + 5, hour: 23),
            games: [game1, game2],
            stadiums: stadiums
        )
    }

    // MARK: - 10.1: Concurrent Requests Test

    @Test("10.1 - Concurrent requests behavior documentation")
    func test_engine_ConcurrentRequests_CurrentlyUnsafe() async {
        // DOCUMENTATION TEST
        // Purpose: Document the current behavior when TripPlanningEngine is called concurrently.
        //
        // Current Implementation Status:
        // - TripPlanningEngine is a `final class` (not an actor)
        // - It appears stateless - no mutable instance state persists between calls
        // - Each call to planItineraries creates fresh planner instances
        //
        // Expected Behavior:
        // - If truly stateless: concurrent calls should succeed independently
        // - If hidden state exists: may see race conditions or crashes
        //
        // This test documents the current behavior for future refactoring reference.

        let concurrentRequestCount = 10
        let engine = TripPlanningEngine()

        // Create unique requests for each concurrent task
        let requests = (0..<concurrentRequestCount).map { makeValidTestRequest(requestIndex: $0) }

        // Execute all requests concurrently using TaskGroup
        let results = await withTaskGroup(of: (Int, ItineraryResult).self) { group in
            for (index, request) in requests.enumerated() {
                group.addTask {
                    let result = engine.planItineraries(request: request)
                    return (index, result)
                }
            }

            var collected: [(Int, ItineraryResult)] = []
            for await result in group {
                collected.append(result)
            }
            return collected.sorted { $0.0 < $1.0 }
        }

        // Document the observed behavior
        let successCount = results.filter { $0.1.isSuccess }.count
        let failureCount = results.filter { !$0.1.isSuccess }.count

        // Current observation: Engine appears stateless, so concurrent calls should work
        // If this test fails in the future, it indicates hidden shared state was introduced

        // We expect most/all requests to succeed since the engine is stateless
        // Allow for some failures due to specific request constraints
        #expect(results.count == concurrentRequestCount,
               "All concurrent requests should complete (got \(results.count)/\(concurrentRequestCount))")

        // Document: Current implementation handles concurrent requests
        // because planItineraries() creates fresh planners per call
        #expect(successCount > 0,
               "At least some concurrent requests should succeed (success: \(successCount), failure: \(failureCount))")

        // Note for future refactoring:
        // If actor-based refactoring is done, update this test to verify:
        // 1. Proper isolation of mutable state
        // 2. Consistent results regardless of concurrent access
        // 3. No deadlocks under high concurrency
    }

    // MARK: - 10.2: Sequential Requests Test

    @Test("10.2 - Sequential requests succeed consistently")
    func test_engine_SequentialRequests_Succeeds() {
        // BASELINE TEST
        // Purpose: Verify that sequential requests to the same engine instance
        // always succeed when given valid input.
        //
        // This establishes the baseline behavior that any concurrency-safe
        // refactoring must preserve.

        let sequentialRequestCount = 10
        let engine = TripPlanningEngine()

        var results: [ItineraryResult] = []

        // Execute requests sequentially
        for index in 0..<sequentialRequestCount {
            let request = makeValidTestRequest(requestIndex: index)
            let result = engine.planItineraries(request: request)
            results.append(result)
        }

        // All sequential requests should complete
        #expect(results.count == sequentialRequestCount,
               "All sequential requests should complete")

        // With valid input, all requests should succeed
        let successCount = results.filter { $0.isSuccess }.count

        #expect(successCount == sequentialRequestCount,
               "All sequential requests with valid input should succeed (got \(successCount)/\(sequentialRequestCount))")

        // Verify each successful result has valid data
        for (index, result) in results.enumerated() {
            if result.isSuccess {
                #expect(!result.options.isEmpty,
                       "Request \(index) should return at least one route option")

                for option in result.options {
                    #expect(!option.stops.isEmpty,
                           "Request \(index) options should have stops")
                }
            }
        }
    }
}
