//
//  FixtureGenerator.swift
//  SportsTimeTests
//
//  Generates synthetic test data for unit and integration tests.
//  Uses deterministic seeding for reproducible test results.
//

import Foundation
import CoreLocation
@testable import SportsTime

// MARK: - Random Number Generator with Seed

struct SeededRandomNumberGenerator: RandomNumberGenerator {
    private var state: UInt64

    init(seed: UInt64) {
        self.state = seed
    }

    mutating func next() -> UInt64 {
        // xorshift64 algorithm for reproducibility
        state ^= state << 13
        state ^= state >> 7
        state ^= state << 17
        return state
    }
}

// MARK: - Fixture Generator

struct FixtureGenerator {

    // MARK: - Configuration

    struct Configuration {
        var seed: UInt64 = 12345
        var gameCount: Int = 50
        var stadiumCount: Int = 30
        var teamCount: Int = 30
        var dateRange: ClosedRange<Date> = {
            let start = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 1))!
            let end = Calendar.current.date(from: DateComponents(year: 2026, month: 9, day: 30))!
            return start...end
        }()
        var sports: Set<Sport> = [.mlb, .nba, .nhl]
        var geographicSpread: GeographicSpread = .nationwide

        enum GeographicSpread {
            case nationwide      // Full US coverage
            case regional        // Concentrated in one region
            case corridor        // Along a route (e.g., East Coast)
            case cluster         // Single metro area
        }

        static var `default`: Configuration { Configuration() }
        static var minimal: Configuration { Configuration(gameCount: 5, stadiumCount: 5, teamCount: 5) }
        static var small: Configuration { Configuration(gameCount: 50, stadiumCount: 15, teamCount: 15) }
        static var medium: Configuration { Configuration(gameCount: 500, stadiumCount: 30, teamCount: 30) }
        static var large: Configuration { Configuration(gameCount: 2000, stadiumCount: 30, teamCount: 60) }
        static var stress: Configuration { Configuration(gameCount: 10000, stadiumCount: 30, teamCount: 60) }
    }

    // MARK: - Generated Data Container

    struct GeneratedData {
        let stadiums: [Stadium]
        let teams: [Team]
        let games: [Game]
        let stadiumsById: [UUID: Stadium]
        let teamsById: [UUID: Team]

        func richGame(from game: Game) -> RichGame? {
            guard let homeTeam = teamsById[game.homeTeamId],
                  let awayTeam = teamsById[game.awayTeamId],
                  let stadium = stadiumsById[game.stadiumId] else {
                return nil
            }
            return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
        }

        func richGames() -> [RichGame] {
            games.compactMap { richGame(from: $0) }
        }
    }

    // MARK: - City Data for Realistic Generation

    private static let cityData: [(name: String, state: String, lat: Double, lon: Double, region: Region)] = [
        // East Coast
        ("New York", "NY", 40.7128, -73.9352, .east),
        ("Boston", "MA", 42.3601, -71.0589, .east),
        ("Philadelphia", "PA", 39.9526, -75.1652, .east),
        ("Washington", "DC", 38.9072, -77.0369, .east),
        ("Baltimore", "MD", 39.2904, -76.6122, .east),
        ("Miami", "FL", 25.7617, -80.1918, .east),
        ("Tampa", "FL", 27.9506, -82.4572, .east),
        ("Atlanta", "GA", 33.7490, -84.3880, .east),
        ("Charlotte", "NC", 35.2271, -80.8431, .east),
        ("Pittsburgh", "PA", 40.4406, -79.9959, .east),

        // Central
        ("Chicago", "IL", 41.8781, -87.6298, .central),
        ("Detroit", "MI", 42.3314, -83.0458, .central),
        ("Cleveland", "OH", 41.4993, -81.6944, .central),
        ("Cincinnati", "OH", 39.1031, -84.5120, .central),
        ("Milwaukee", "WI", 43.0389, -87.9065, .central),
        ("Minneapolis", "MN", 44.9778, -93.2650, .central),
        ("St. Louis", "MO", 38.6270, -90.1994, .central),
        ("Kansas City", "MO", 39.0997, -94.5786, .central),
        ("Dallas", "TX", 32.7767, -96.7970, .central),
        ("Houston", "TX", 29.7604, -95.3698, .central),

        // West Coast
        ("Los Angeles", "CA", 34.0522, -118.2437, .west),
        ("San Francisco", "CA", 37.7749, -122.4194, .west),
        ("San Diego", "CA", 32.7157, -117.1611, .west),
        ("Seattle", "WA", 47.6062, -122.3321, .west),
        ("Portland", "OR", 45.5152, -122.6784, .west),
        ("Phoenix", "AZ", 33.4484, -112.0740, .west),
        ("Denver", "CO", 39.7392, -104.9903, .west),
        ("Salt Lake City", "UT", 40.7608, -111.8910, .west),
        ("Las Vegas", "NV", 36.1699, -115.1398, .west),
        ("Oakland", "CA", 37.8044, -122.2712, .west),
    ]

    private static let teamNames = [
        "Eagles", "Tigers", "Bears", "Lions", "Panthers",
        "Hawks", "Wolves", "Sharks", "Dragons", "Knights",
        "Royals", "Giants", "Cardinals", "Mariners", "Brewers",
        "Rangers", "Padres", "Dodgers", "Mets", "Yankees",
        "Cubs", "Sox", "Twins", "Rays", "Marlins",
        "Nationals", "Braves", "Reds", "Pirates", "Orioles"
    ]

    // MARK: - Generation

    static func generate(with config: Configuration = .default) -> GeneratedData {
        var rng = SeededRandomNumberGenerator(seed: config.seed)

        // Generate stadiums
        let stadiums = generateStadiums(config: config, rng: &rng)
        let stadiumsById = Dictionary(uniqueKeysWithValues: stadiums.map { ($0.id, $0) })

        // Generate teams (2 per stadium typically)
        let teams = generateTeams(stadiums: stadiums, config: config, rng: &rng)
        let teamsById = Dictionary(uniqueKeysWithValues: teams.map { ($0.id, $0) })

        // Generate games
        let games = generateGames(teams: teams, stadiums: stadiums, config: config, rng: &rng)

        return GeneratedData(
            stadiums: stadiums,
            teams: teams,
            games: games,
            stadiumsById: stadiumsById,
            teamsById: teamsById
        )
    }

    private static func generateStadiums(
        config: Configuration,
        rng: inout SeededRandomNumberGenerator
    ) -> [Stadium] {
        let cities = selectCities(for: config.geographicSpread, count: config.stadiumCount, rng: &rng)

        return cities.enumerated().map { index, city in
            let sport = config.sports.randomElement(using: &rng) ?? .mlb
            return Stadium(
                id: UUID(),
                name: "\(city.name) \(sport.rawValue) Stadium",
                city: city.name,
                state: city.state,
                latitude: city.lat + Double.random(in: -0.05...0.05, using: &rng),
                longitude: city.lon + Double.random(in: -0.05...0.05, using: &rng),
                capacity: Int.random(in: 20000...60000, using: &rng),
                sport: sport,
                yearOpened: Int.random(in: 1990...2024, using: &rng)
            )
        }
    }

    private static func generateTeams(
        stadiums: [Stadium],
        config: Configuration,
        rng: inout SeededRandomNumberGenerator
    ) -> [Team] {
        var teams: [Team] = []
        var usedNames = Set<String>()

        for stadium in stadiums {
            // Each stadium gets 1-2 teams
            let teamCountForStadium = Int.random(in: 1...2, using: &rng)

            for _ in 0..<teamCountForStadium {
                guard teams.count < config.teamCount else { break }

                var teamName: String
                repeat {
                    teamName = teamNames.randomElement(using: &rng) ?? "Team\(teams.count)"
                } while usedNames.contains("\(stadium.city) \(teamName)")

                usedNames.insert("\(stadium.city) \(teamName)")

                teams.append(Team(
                    id: UUID(),
                    name: teamName,
                    abbreviation: String(teamName.prefix(3)).uppercased(),
                    sport: stadium.sport,
                    city: stadium.city,
                    stadiumId: stadium.id
                ))
            }
        }

        return teams
    }

    private static func generateGames(
        teams: [Team],
        stadiums: [Stadium],
        config: Configuration,
        rng: inout SeededRandomNumberGenerator
    ) -> [Game] {
        var games: [Game] = []
        let calendar = Calendar.current

        let dateRangeDays = calendar.dateComponents([.day], from: config.dateRange.lowerBound, to: config.dateRange.upperBound).day ?? 180

        for _ in 0..<config.gameCount {
            guard teams.count >= 2 else { break }

            // Pick random home team
            let homeTeam = teams.randomElement(using: &rng)!

            // Pick random away team (different from home)
            var awayTeam: Team
            repeat {
                awayTeam = teams.randomElement(using: &rng)!
            } while awayTeam.id == homeTeam.id

            // Find home team's stadium
            let stadium = stadiums.first { $0.id == homeTeam.stadiumId } ?? stadiums[0]

            // Random date within range
            let daysOffset = Int.random(in: 0..<dateRangeDays, using: &rng)
            let gameDate = calendar.date(byAdding: .day, value: daysOffset, to: config.dateRange.lowerBound)!

            // Random game time (1pm - 9pm)
            let hour = Int.random(in: 13...21, using: &rng)
            let gameDateTime = calendar.date(bySettingHour: hour, minute: 0, second: 0, of: gameDate)!

            games.append(Game(
                id: UUID(),
                homeTeamId: homeTeam.id,
                awayTeamId: awayTeam.id,
                stadiumId: stadium.id,
                dateTime: gameDateTime,
                sport: homeTeam.sport,
                season: "2026",
                isPlayoff: Double.random(in: 0...1, using: &rng) < 0.1 // 10% playoff games
            ))
        }

        return games.sorted { $0.dateTime < $1.dateTime }
    }

    private static func selectCities(
        for spread: Configuration.GeographicSpread,
        count: Int,
        rng: inout SeededRandomNumberGenerator
    ) -> [(name: String, state: String, lat: Double, lon: Double, region: Region)] {
        let cities: [(name: String, state: String, lat: Double, lon: Double, region: Region)]

        switch spread {
        case .nationwide:
            cities = cityData.shuffled(using: &rng)
        case .regional:
            let region = Region.allCases.randomElement(using: &rng) ?? .east
            cities = cityData.filter { $0.region == region }.shuffled(using: &rng)
        case .corridor:
            // East Coast corridor
            cities = cityData.filter { $0.region == .east }.shuffled(using: &rng)
        case .cluster:
            // Just pick one city and create variations
            let baseCity = cityData.randomElement(using: &rng)!
            cities = (0..<count).map { i in
                (
                    name: "\(baseCity.name) \(i)",
                    state: baseCity.state,
                    lat: baseCity.lat + Double.random(in: -0.5...0.5, using: &rng),
                    lon: baseCity.lon + Double.random(in: -0.5...0.5, using: &rng),
                    region: baseCity.region
                )
            }
            return Array(cities.prefix(count))
        }

        return Array(cities.prefix(count))
    }

    // MARK: - Specialized Generators

    /// Generate a trip with specific configuration
    static func generateTrip(
        stops: Int = 5,
        startDate: Date = Date(),
        preferences: TripPreferences? = nil,
        rng: inout SeededRandomNumberGenerator
    ) -> Trip {
        let prefs = preferences ?? TripPreferences(
            planningMode: .dateRange,
            sports: [.mlb],
            startDate: startDate,
            endDate: Calendar.current.date(byAdding: .day, value: stops * 2, to: startDate)!
        )

        var tripStops: [TripStop] = []
        var currentDate = startDate

        for i in 0..<stops {
            let city = cityData.randomElement(using: &rng)!
            let departureDate = Calendar.current.date(byAdding: .day, value: 1, to: currentDate)!

            tripStops.append(TripStop(
                id: UUID(),
                stopNumber: i + 1,
                city: city.name,
                state: city.state,
                coordinate: CLLocationCoordinate2D(latitude: city.lat, longitude: city.lon),
                arrivalDate: currentDate,
                departureDate: departureDate,
                games: [UUID()],
                stadium: UUID()
            ))

            currentDate = departureDate
        }

        return Trip(
            name: "Test Trip",
            preferences: prefs,
            stops: tripStops
        )
    }

    /// Generate games specifically for testing same-day conflicts
    static func generateConflictingGames(
        date: Date,
        cities: [(name: String, lat: Double, lon: Double)],
        rng: inout SeededRandomNumberGenerator
    ) -> [Game] {
        cities.map { city in
            let stadiumId = UUID()
            return Game(
                id: UUID(),
                homeTeamId: UUID(),
                awayTeamId: UUID(),
                stadiumId: stadiumId,
                dateTime: date,
                sport: .mlb,
                season: "2026"
            )
        }
    }

    /// Generate a stadium at a specific location
    static func makeStadium(
        id: UUID = UUID(),
        name: String = "Test Stadium",
        city: String = "Test City",
        state: String = "TS",
        latitude: Double = 40.0,
        longitude: Double = -100.0,
        capacity: Int = 40000,
        sport: Sport = .mlb
    ) -> Stadium {
        Stadium(
            id: id,
            name: name,
            city: city,
            state: state,
            latitude: latitude,
            longitude: longitude,
            capacity: capacity,
            sport: sport
        )
    }

    /// Generate a team
    static func makeTeam(
        id: UUID = UUID(),
        name: String = "Test Team",
        abbreviation: String = "TST",
        sport: Sport = .mlb,
        city: String = "Test City",
        stadiumId: UUID
    ) -> Team {
        Team(
            id: id,
            name: name,
            abbreviation: abbreviation,
            sport: sport,
            city: city,
            stadiumId: stadiumId
        )
    }

    /// Generate a game
    static func makeGame(
        id: UUID = UUID(),
        homeTeamId: UUID,
        awayTeamId: UUID,
        stadiumId: UUID,
        dateTime: Date = Date(),
        sport: Sport = .mlb,
        season: String = "2026",
        isPlayoff: Bool = false
    ) -> Game {
        Game(
            id: id,
            homeTeamId: homeTeamId,
            awayTeamId: awayTeamId,
            stadiumId: stadiumId,
            dateTime: dateTime,
            sport: sport,
            season: season,
            isPlayoff: isPlayoff
        )
    }

    /// Generate a travel segment
    static func makeTravelSegment(
        from: LocationInput,
        to: LocationInput,
        distanceMiles: Double = 100,
        durationHours: Double = 2
    ) -> TravelSegment {
        TravelSegment(
            fromLocation: from,
            toLocation: to,
            travelMode: .drive,
            distanceMeters: distanceMiles * TestConstants.metersPerMile,
            durationSeconds: durationHours * 3600
        )
    }

    /// Generate a trip stop
    static func makeTripStop(
        stopNumber: Int = 1,
        city: String = "Test City",
        state: String = "TS",
        coordinate: CLLocationCoordinate2D? = nil,
        arrivalDate: Date = Date(),
        departureDate: Date? = nil,
        games: [UUID] = [],
        stadium: UUID? = nil,
        isRestDay: Bool = false
    ) -> TripStop {
        TripStop(
            stopNumber: stopNumber,
            city: city,
            state: state,
            coordinate: coordinate,
            arrivalDate: arrivalDate,
            departureDate: departureDate ?? Calendar.current.date(byAdding: .day, value: 1, to: arrivalDate)!,
            games: games,
            stadium: stadium,
            isRestDay: isRestDay
        )
    }

    // MARK: - Known Locations for Testing

    struct KnownLocations {
        static let nyc = CLLocationCoordinate2D(latitude: 40.7128, longitude: -73.9352)
        static let la = CLLocationCoordinate2D(latitude: 34.0522, longitude: -118.2437)
        static let chicago = CLLocationCoordinate2D(latitude: 41.8781, longitude: -87.6298)
        static let boston = CLLocationCoordinate2D(latitude: 42.3601, longitude: -71.0589)
        static let miami = CLLocationCoordinate2D(latitude: 25.7617, longitude: -80.1918)
        static let seattle = CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321)
        static let denver = CLLocationCoordinate2D(latitude: 39.7392, longitude: -104.9903)

        // Antipodal point (for testing haversine at extreme distances)
        static let antipodal = CLLocationCoordinate2D(latitude: -40.7128, longitude: 106.0648)
    }
}
