//
//  TestFixtures.swift
//  SportsTimeTests
//
//  Factory methods for creating test data. These fixtures create realistic
//  domain objects with sensible defaults that can be customized per test.
//
//  Usage:
//    let game = TestFixtures.game()  // Default game
//    let game = TestFixtures.game(sport: .nba, city: "Boston")  // Customized
//

import Foundation
import CoreLocation
@testable import SportsTime

// MARK: - Test Fixtures

enum TestFixtures {

    // MARK: - Reference Data

    /// Real stadium coordinates for realistic distance calculations
    static let coordinates: [String: CLLocationCoordinate2D] = [
        "New York": CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855),      // NYC (Midtown)
        "Boston": CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972),        // Fenway Park
        "Chicago": CLLocationCoordinate2D(latitude: 41.9484, longitude: -87.6553),       // Wrigley Field
        "Los Angeles": CLLocationCoordinate2D(latitude: 34.0739, longitude: -118.2400),  // Dodger Stadium
        "San Francisco": CLLocationCoordinate2D(latitude: 37.7786, longitude: -122.3893), // Oracle Park
        "Seattle": CLLocationCoordinate2D(latitude: 47.5914, longitude: -122.3325),      // T-Mobile Park
        "Denver": CLLocationCoordinate2D(latitude: 39.7559, longitude: -104.9942),       // Coors Field
        "Houston": CLLocationCoordinate2D(latitude: 29.7573, longitude: -95.3555),       // Minute Maid
        "Miami": CLLocationCoordinate2D(latitude: 25.7781, longitude: -80.2197),         // LoanDepot Park
        "Atlanta": CLLocationCoordinate2D(latitude: 33.7553, longitude: -84.4006),       // Truist Park
        "Phoenix": CLLocationCoordinate2D(latitude: 33.4455, longitude: -112.0667),      // Chase Field
        "Dallas": CLLocationCoordinate2D(latitude: 32.7473, longitude: -97.0945),        // Globe Life Field
        "Philadelphia": CLLocationCoordinate2D(latitude: 39.9061, longitude: -75.1665),  // Citizens Bank
        "Detroit": CLLocationCoordinate2D(latitude: 42.3390, longitude: -83.0485),       // Comerica Park
        "Minneapolis": CLLocationCoordinate2D(latitude: 44.9817, longitude: -93.2776),   // Target Field
    ]

    /// Time zones for realistic local time testing
    static let timeZones: [String: String] = [
        "New York": "America/New_York",
        "Boston": "America/New_York",
        "Chicago": "America/Chicago",
        "Los Angeles": "America/Los_Angeles",
        "San Francisco": "America/Los_Angeles",
        "Seattle": "America/Los_Angeles",
        "Denver": "America/Denver",
        "Houston": "America/Chicago",
        "Miami": "America/New_York",
        "Atlanta": "America/New_York",
        "Phoenix": "America/Phoenix",
        "Dallas": "America/Chicago",
        "Philadelphia": "America/New_York",
        "Detroit": "America/Detroit",
        "Minneapolis": "America/Chicago",
    ]

    /// State abbreviations
    static let states: [String: String] = [
        "New York": "NY", "Boston": "MA", "Chicago": "IL",
        "Los Angeles": "CA", "San Francisco": "CA", "Seattle": "WA",
        "Denver": "CO", "Houston": "TX", "Miami": "FL", "Atlanta": "GA",
        "Phoenix": "AZ", "Dallas": "TX", "Philadelphia": "PA",
        "Detroit": "MI", "Minneapolis": "MN",
    ]

    // MARK: - Game Factory

    /// Creates a Game with realistic defaults.
    ///
    /// - Expected Behavior:
    ///   - Returns a valid Game with all required fields populated
    ///   - ID follows canonical format: "game_{sport}_{season}_{away}_{home}_{mmdd}"
    ///   - DateTime defaults to noon tomorrow in specified city's timezone
    static func game(
        id: String? = nil,
        sport: Sport = .mlb,
        city: String = "New York",
        dateTime: Date? = nil,
        homeTeamId: String? = nil,
        awayTeamId: String? = nil,
        stadiumId: String? = nil,
        season: String = "2026",
        isPlayoff: Bool = false
    ) -> Game {
        let actualDateTime = dateTime ?? Calendar.current.date(byAdding: .day, value: 1, to: Date())!
        let homeId = homeTeamId ?? "team_\(sport.rawValue.lowercased())_\(city.lowercased().replacingOccurrences(of: " ", with: "_"))"
        let awayId = awayTeamId ?? "team_\(sport.rawValue.lowercased())_visitor"
        let stadId = stadiumId ?? "stadium_\(sport.rawValue.lowercased())_\(city.lowercased().replacingOccurrences(of: " ", with: "_"))"

        let formatter = DateFormatter()
        formatter.dateFormat = "MMdd"
        let dateStr = formatter.string(from: actualDateTime)

        let actualId = id ?? "game_\(sport.rawValue.lowercased())_\(season)_\(awayId.split(separator: "_").last ?? "vis")_\(homeId.split(separator: "_").last ?? "home")_\(dateStr)"

        return Game(
            id: actualId,
            homeTeamId: homeId,
            awayTeamId: awayId,
            stadiumId: stadId,
            dateTime: actualDateTime,
            sport: sport,
            season: season,
            isPlayoff: isPlayoff
        )
    }

    /// Creates multiple games spread across time and cities.
    ///
    /// - Parameter count: Number of games to create
    /// - Parameter cities: Cities to distribute games across (cycles through list)
    /// - Parameter startDate: First game date (subsequent games spread by daySpread)
    /// - Parameter daySpread: Days between games
    static func games(
        count: Int,
        sport: Sport = .mlb,
        cities: [String] = ["New York", "Boston", "Chicago", "Los Angeles"],
        startDate: Date = Date(),
        daySpread: Int = 1
    ) -> [Game] {
        (0..<count).map { i in
            let city = cities[i % cities.count]
            let gameDate = Calendar.current.date(byAdding: .day, value: i * daySpread, to: startDate)!
            return game(sport: sport, city: city, dateTime: gameDate)
        }
    }

    /// Creates games for same-day conflict testing.
    static func sameDayGames(
        cities: [String],
        date: Date = Date(),
        sport: Sport = .mlb
    ) -> [Game] {
        cities.enumerated().map { index, city in
            // Stagger times by 3 hours
            let time = Calendar.current.date(byAdding: .hour, value: 13 + (index * 3), to: Calendar.current.startOfDay(for: date))!
            return game(sport: sport, city: city, dateTime: time)
        }
    }

    // MARK: - Stadium Factory

    /// Creates a Stadium with realistic defaults.
    ///
    /// - Expected Behavior:
    ///   - Uses real coordinates for known cities
    ///   - ID follows canonical format: "stadium_{sport}_{city}"
    ///   - TimeZone populated for known cities
    static func stadium(
        id: String? = nil,
        name: String? = nil,
        city: String = "New York",
        state: String? = nil,
        sport: Sport = .mlb,
        capacity: Int = 40000,
        yearOpened: Int? = nil
    ) -> Stadium {
        let coordinate = coordinates[city] ?? CLLocationCoordinate2D(latitude: 40.0, longitude: -74.0)
        let actualState = state ?? states[city] ?? "NY"
        let actualName = name ?? "\(city) \(sport.rawValue) Stadium"
        let actualId = id ?? "stadium_\(sport.rawValue.lowercased())_\(city.lowercased().replacingOccurrences(of: " ", with: "_"))"

        return Stadium(
            id: actualId,
            name: actualName,
            city: city,
            state: actualState,
            latitude: coordinate.latitude,
            longitude: coordinate.longitude,
            capacity: capacity,
            sport: sport,
            yearOpened: yearOpened,
            timeZoneIdentifier: timeZones[city]
        )
    }

    /// Creates a stadium map for a set of games.
    static func stadiumMap(for games: [Game]) -> [String: Stadium] {
        var map: [String: Stadium] = [:]
        for game in games {
            if map[game.stadiumId] == nil {
                // Extract city from stadium ID (assumes format stadium_sport_city)
                let parts = game.stadiumId.split(separator: "_")
                let city = parts.count > 2 ? parts[2...].joined(separator: " ").capitalized : "Unknown"
                map[game.stadiumId] = stadium(id: game.stadiumId, city: city, sport: game.sport)
            }
        }
        return map
    }

    /// Creates stadiums at specific coordinates for distance testing.
    static func stadiumsForDistanceTest() -> [Stadium] {
        [
            stadium(city: "New York"),      // East
            stadium(city: "Chicago"),       // Central
            stadium(city: "Denver"),        // Mountain
            stadium(city: "Los Angeles"),   // West
        ]
    }

    // MARK: - Team Factory

    /// Creates a Team with realistic defaults.
    static func team(
        id: String? = nil,
        name: String = "Test Team",
        abbreviation: String? = nil,
        sport: Sport = .mlb,
        city: String = "New York",
        stadiumId: String? = nil
    ) -> Team {
        let actualId = id ?? "team_\(sport.rawValue.lowercased())_\(city.lowercased().replacingOccurrences(of: " ", with: "_"))"
        let actualAbbr = abbreviation ?? String(city.prefix(3)).uppercased()
        let actualStadiumId = stadiumId ?? "stadium_\(sport.rawValue.lowercased())_\(city.lowercased().replacingOccurrences(of: " ", with: "_"))"

        return Team(
            id: actualId,
            name: name,
            abbreviation: actualAbbr,
            sport: sport,
            city: city,
            stadiumId: actualStadiumId
        )
    }

    // MARK: - TripStop Factory

    /// Creates a TripStop with realistic defaults.
    static func tripStop(
        stopNumber: Int = 1,
        city: String = "New York",
        state: String? = nil,
        arrivalDate: Date? = nil,
        departureDate: Date? = nil,
        games: [String] = [],
        isRestDay: Bool = false
    ) -> TripStop {
        let coordinate = coordinates[city]
        let actualState = state ?? states[city] ?? "NY"
        let arrival = arrivalDate ?? Date()
        let departure = departureDate ?? Calendar.current.date(byAdding: .day, value: 1, to: arrival)!

        return TripStop(
            stopNumber: stopNumber,
            city: city,
            state: actualState,
            coordinate: coordinate,
            arrivalDate: arrival,
            departureDate: departure,
            games: games,
            isRestDay: isRestDay
        )
    }

    /// Creates a sequence of trip stops for a multi-city trip.
    static func tripStops(
        cities: [String],
        startDate: Date = Date(),
        daysPerStop: Int = 1
    ) -> [TripStop] {
        var stops: [TripStop] = []
        var currentDate = startDate

        for (index, city) in cities.enumerated() {
            let departure = Calendar.current.date(byAdding: .day, value: daysPerStop, to: currentDate)!
            stops.append(tripStop(
                stopNumber: index + 1,
                city: city,
                arrivalDate: currentDate,
                departureDate: departure
            ))
            currentDate = departure
        }
        return stops
    }

    // MARK: - TravelSegment Factory

    /// Creates a TravelSegment between two cities.
    static func travelSegment(
        from: String = "New York",
        to: String = "Boston",
        travelMode: TravelMode = .drive
    ) -> TravelSegment {
        let fromCoord = coordinates[from] ?? CLLocationCoordinate2D(latitude: 40.0, longitude: -74.0)
        let toCoord = coordinates[to] ?? CLLocationCoordinate2D(latitude: 42.0, longitude: -71.0)

        // Calculate approximate distance (haversine)
        let distance = haversineDistance(from: fromCoord, to: toCoord)
        // Estimate driving time at 60 mph average
        let duration = distance / 60.0 * 3600.0

        return TravelSegment(
            fromLocation: LocationInput(name: from, coordinate: fromCoord),
            toLocation: LocationInput(name: to, coordinate: toCoord),
            travelMode: travelMode,
            distanceMeters: distance * 1609.34, // miles to meters
            durationSeconds: duration
        )
    }

    // MARK: - TripPreferences Factory

    /// Creates TripPreferences with common defaults.
    static func preferences(
        mode: PlanningMode = .dateRange,
        sports: Set<Sport> = [.mlb],
        startDate: Date? = nil,
        endDate: Date? = nil,
        regions: Set<Region> = [.east, .central, .west],
        leisureLevel: LeisureLevel = .moderate,
        travelMode: TravelMode = .drive,
        needsEVCharging: Bool = false,
        maxDrivingHoursPerDriver: Double? = nil
    ) -> TripPreferences {
        let start = startDate ?? Date()
        let end = endDate ?? Calendar.current.date(byAdding: .day, value: 7, to: start)!

        return TripPreferences(
            planningMode: mode,
            sports: sports,
            travelMode: travelMode,
            startDate: start,
            endDate: end,
            leisureLevel: leisureLevel,
            routePreference: .balanced,
            needsEVCharging: needsEVCharging,
            maxDrivingHoursPerDriver: maxDrivingHoursPerDriver,
            selectedRegions: regions
        )
    }

    // MARK: - Trip Factory

    /// Creates a complete Trip with stops and segments.
    static func trip(
        name: String = "Test Trip",
        stops: [TripStop]? = nil,
        preferences: TripPreferences? = nil,
        status: TripStatus = .planned
    ) -> Trip {
        let actualStops = stops ?? tripStops(cities: ["New York", "Boston"])
        let actualPrefs = preferences ?? TestFixtures.preferences()

        // Calculate totals from stops
        let totalGames = actualStops.reduce(0) { $0 + $1.games.count }

        return Trip(
            name: name,
            preferences: actualPrefs,
            stops: actualStops,
            totalGames: totalGames,
            status: status
        )
    }

    // MARK: - RichGame Factory

    /// Creates a RichGame with resolved team and stadium references.
    static func richGame(
        game: Game? = nil,
        homeCity: String = "New York",
        awayCity: String = "Boston",
        sport: Sport = .mlb
    ) -> RichGame {
        let actualGame = game ?? TestFixtures.game(sport: sport, city: homeCity)
        let homeTeam = team(sport: sport, city: homeCity)
        let awayTeam = team(sport: sport, city: awayCity)
        let gameStadium = stadium(city: homeCity, sport: sport)

        return RichGame(
            game: actualGame,
            homeTeam: homeTeam,
            awayTeam: awayTeam,
            stadium: gameStadium
        )
    }

    // MARK: - TripScore Factory

    /// Creates a TripScore with customizable component scores.
    static func tripScore(
        overall: Double = 85.0,
        gameQuality: Double = 90.0,
        routeEfficiency: Double = 80.0,
        leisureBalance: Double = 85.0,
        preferenceAlignment: Double = 85.0
    ) -> TripScore {
        TripScore(
            overallScore: overall,
            gameQualityScore: gameQuality,
            routeEfficiencyScore: routeEfficiency,
            leisureBalanceScore: leisureBalance,
            preferenceAlignmentScore: preferenceAlignment
        )
    }

    // MARK: - Date Helpers

    /// Creates a date at a specific time (for testing time-sensitive logic).
    static func date(
        year: Int = 2026,
        month: Int = 6,
        day: Int = 15,
        hour: Int = 19,
        minute: Int = 5
    ) -> Date {
        var components = DateComponents()
        components.year = year
        components.month = month
        components.day = day
        components.hour = hour
        components.minute = minute
        components.timeZone = TimeZone(identifier: "America/New_York")
        return Calendar.current.date(from: components)!
    }

    /// Creates dates for a range of days.
    static func dateRange(start: Date = Date(), days: Int) -> (start: Date, end: Date) {
        let end = Calendar.current.date(byAdding: .day, value: days, to: start)!
        return (start, end)
    }

    // MARK: - Private Helpers

    /// Haversine distance calculation (returns miles).
    private static func haversineDistance(
        from: CLLocationCoordinate2D,
        to: CLLocationCoordinate2D
    ) -> Double {
        let R = 3958.8 // Earth radius in miles
        let lat1 = from.latitude * .pi / 180
        let lat2 = to.latitude * .pi / 180
        let deltaLat = (to.latitude - from.latitude) * .pi / 180
        let deltaLon = (to.longitude - from.longitude) * .pi / 180

        let a = sin(deltaLat / 2) * sin(deltaLat / 2) +
                cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2)
        let c = 2 * atan2(sqrt(a), sqrt(1 - a))

        return R * c
    }
}

// MARK: - Coordinate Constants for Testing

extension TestFixtures {

    /// Known distances between cities (in miles) for validation.
    static let knownDistances: [(from: String, to: String, miles: Double)] = [
        ("New York", "Boston", 215),
        ("New York", "Chicago", 790),
        ("New York", "Los Angeles", 2790),
        ("Chicago", "Denver", 1000),
        ("Los Angeles", "San Francisco", 380),
        ("Seattle", "Los Angeles", 1135),
    ]

    /// Cities clearly in each region for boundary testing.
    static let eastCoastCities = ["New York", "Boston", "Miami", "Atlanta", "Philadelphia"]
    static let centralCities = ["Chicago", "Houston", "Dallas", "Minneapolis", "Detroit"]
    static let westCoastCities = ["Los Angeles", "San Francisco", "Seattle", "Phoenix"]
}
