//
//  TripCreationViewModel.swift
//  SportsTime
//

import Foundation
import SwiftUI
import Observation
import CoreLocation

@Observable
final class TripCreationViewModel {

    // MARK: - State

    enum ViewState: Equatable {
        case editing
        case planning
        case selectingOption([ItineraryOption])  // Multiple options to choose from
        case completed(Trip)
        case error(String)

        static func == (lhs: ViewState, rhs: ViewState) -> Bool {
            switch (lhs, rhs) {
            case (.editing, .editing): return true
            case (.planning, .planning): return true
            case (.selectingOption(let o1), .selectingOption(let o2)): return o1.count == o2.count
            case (.completed(let t1), .completed(let t2)): return t1.id == t2.id
            case (.error(let e1), .error(let e2)): return e1 == e2
            default: return false
            }
        }
    }

    var viewState: ViewState = .editing

    // MARK: - Planning Mode

    var planningMode: PlanningMode = .dateRange {
        didSet {
            // Reset state when mode changes to ensure clean UI transitions
            if oldValue != planningMode {
                viewState = .editing
                // Clear mode-specific selections
                switch planningMode {
                case .dateRange, .gameFirst:
                    // Clear locations for date-based modes
                    startLocationText = ""
                    endLocationText = ""
                    startLocation = nil
                    endLocation = nil
                case .locations:
                    // Keep locations (user needs to enter them)
                    break
                case .followTeam:
                    // Clear locations and must-see games for follow team mode
                    startLocationText = ""
                    endLocationText = ""
                    startLocation = nil
                    endLocation = nil
                    mustSeeGameIds.removeAll()
                }
            }
        }
    }

    // MARK: - Form Fields

    // Locations (used in .locations mode)
    var startLocationText: String = ""
    var endLocationText: String = ""
    var startLocation: LocationInput?
    var endLocation: LocationInput?

    // Sports
    var selectedSports: Set<Sport> = [.mlb] {
        didSet {
            // Clear cached games when sports selection changes
            if selectedSports != oldValue {
                availableGames = []
                games = []
            }
        }
    }

    // Dates
    var startDate: Date = Date() {
        didSet {
            // Clear cached games when start date changes
            // BUT: In gameFirst mode, games are the source of truth for dates,
            // so don't clear them (fixes "date range required" error)
            if !Calendar.current.isDate(startDate, inSameDayAs: oldValue) && planningMode != .gameFirst {
                availableGames = []
                games = []
            }
        }
    }
    var endDate: Date = Date().addingTimeInterval(86400 * 7) {
        didSet {
            // Clear cached games when end date changes
            // BUT: In gameFirst mode, games are the source of truth for dates,
            // so don't clear them (fixes "date range required" error)
            if !Calendar.current.isDate(endDate, inSameDayAs: oldValue) && planningMode != .gameFirst {
                availableGames = []
                games = []
            }
        }
    }

    // Games
    var mustSeeGameIds: Set<String> = []
    var availableGames: [RichGame] = []
    var isLoadingGames: Bool = false

    // Travel
    var travelMode: TravelMode = .drive
    var routePreference: RoutePreference = .balanced

    // Constraints
    var useStopCount: Bool = true
    var numberOfStops: Int = 5
    var leisureLevel: LeisureLevel = .moderate

    // Optional
    var mustStopLocations: [LocationInput] = []
    var preferredCities: [String] = []
    var needsEVCharging: Bool = false
    var lodgingType: LodgingType = .hotel
    var numberOfDrivers: Int = 1
    var maxDrivingHoursPerDriver: Double = 8

    // Travel Preferences
    var allowRepeatCities: Bool = true
    var selectedRegions: Set<Region> = [.east, .central, .west]

    // Follow Team Mode
    var followTeamId: String?
    var useHomeLocation: Bool = true

    // Game First Mode - Trip duration for sliding windows
    var gameFirstTripDuration: Int = 7

    // MARK: - Dependencies

    private let planningEngine = TripPlanningEngine()
    private let locationService = LocationService.shared
    private let dataProvider = AppDataProvider.shared

    // MARK: - Cached Data

    private var teams: [String: Team] = [:]
    private var stadiums: [String: Stadium] = [:]
    private var games: [Game] = []
    private(set) var currentPreferences: TripPreferences?

    // MARK: - Computed Properties

    var isFormValid: Bool {
        switch planningMode {
        case .dateRange:
            // Need: sports + valid date range
            return !selectedSports.isEmpty && endDate > startDate

        case .gameFirst:
            // Need: at least one selected game + sports
            return !mustSeeGameIds.isEmpty && !selectedSports.isEmpty

        case .locations:
            // Need: start + end locations + sports
            return !startLocationText.isEmpty &&
                   !endLocationText.isEmpty &&
                   !selectedSports.isEmpty

        case .followTeam:
            // Need: team selected + valid date range
            guard followTeamId != nil else { return false }
            guard endDate > startDate else { return false }
            // If using home location, need a valid start location
            if useHomeLocation && startLocationText.isEmpty { return false }
            return true
        }
    }

    var formValidationMessage: String? {
        switch planningMode {
        case .dateRange:
            if selectedSports.isEmpty { return "Select at least one sport" }
            if endDate <= startDate { return "End date must be after start date" }
        case .gameFirst:
            if mustSeeGameIds.isEmpty { return "Select at least one game" }
            if selectedSports.isEmpty { return "Select at least one sport" }
        case .locations:
            if startLocationText.isEmpty { return "Enter a starting location" }
            if endLocationText.isEmpty { return "Enter an ending location" }
            if selectedSports.isEmpty { return "Select at least one sport" }

        case .followTeam:
            if followTeamId == nil { return "Select a team to follow" }
            if endDate <= startDate { return "End date must be after start date" }
            if useHomeLocation && startLocationText.isEmpty { return "Enter your home location" }
        }
        return nil
    }

    var tripDurationDays: Int {
        let days = Calendar.current.dateComponents([.day], from: startDate, to: endDate).day ?? 0
        return max(1, days)
    }

    var selectedGamesCount: Int {
        mustSeeGameIds.count
    }

    var selectedGames: [RichGame] {
        availableGames.filter { mustSeeGameIds.contains($0.game.id) }
    }

    /// Computed date range for game-first mode based on selected games
    var gameFirstDateRange: (start: Date, end: Date)? {
        guard !selectedGames.isEmpty else { return nil }
        let gameDates = selectedGames.map { $0.game.dateTime }
        guard let earliest = gameDates.min(),
              let latest = gameDates.max() else { return nil }
        return (earliest, latest)
    }

    /// Teams grouped by sport for Follow Team picker
    var teamsBySport: [Sport: [Team]] {
        var grouped: [Sport: [Team]] = [:]
        for team in dataProvider.teams {
            grouped[team.sport, default: []].append(team)
        }
        // Sort teams alphabetically within each sport
        for sport in grouped.keys {
            grouped[sport]?.sort { $0.name < $1.name }
        }
        return grouped
    }

    /// The currently followed team (for display)
    var followedTeam: Team? {
        guard let teamId = followTeamId else { return nil }
        return dataProvider.teams.first { $0.id == teamId }
    }

    // MARK: - Actions

    func loadScheduleData() async {
        do {
            // Ensure initial data is loaded
            if dataProvider.teams.isEmpty {
                await dataProvider.loadInitialData()
            }

            // Use cached teams and stadiums from data provider
            for team in dataProvider.teams {
                teams[team.id] = team
            }
            for stadium in dataProvider.stadiums {
                stadiums[stadium.id] = stadium
            }

            // Filter games within date range
            games = try await dataProvider.filterGames(
                sports: selectedSports,
                startDate: startDate,
                endDate: endDate
            )

            // Build rich games for display
            availableGames = games.compactMap { game -> RichGame? in
                guard let homeTeam = teams[game.homeTeamId],
                      let awayTeam = teams[game.awayTeamId],
                      let stadium = stadiums[game.stadiumId] else {
                    return nil
                }
                return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
            }
        } catch {
            viewState = .error("Failed to load schedule data: \(error.localizedDescription)")
        }
    }

    func resolveLocations() async {
        do {
            // Only resolve if we don't already have a location with coordinates
            // (LocationSearchSheet already provides full LocationInput with coordinates)
            if !startLocationText.isEmpty && startLocation?.coordinate == nil {
                print("🔍 resolveLocations: Resolving startLocation from text '\(startLocationText)'")
                startLocation = try await locationService.resolveLocation(
                    LocationInput(name: startLocationText, address: startLocationText)
                )
            } else if startLocation?.coordinate != nil {
                print("🔍 resolveLocations: startLocation already has coordinates, skipping resolve")
            }

            if !endLocationText.isEmpty && endLocation?.coordinate == nil {
                print("🔍 resolveLocations: Resolving endLocation from text '\(endLocationText)'")
                endLocation = try await locationService.resolveLocation(
                    LocationInput(name: endLocationText, address: endLocationText)
                )
            } else if endLocation?.coordinate != nil {
                print("🔍 resolveLocations: endLocation already has coordinates, skipping resolve")
            }
        } catch {
            viewState = .error("Failed to resolve locations: \(error.localizedDescription)")
        }
    }

    func planTrip() async {
        guard isFormValid else { return }

        viewState = .planning

        // Mode-specific setup
        var effectiveStartDate = startDate
        var effectiveEndDate = endDate
        var resolvedStartLocation: LocationInput?
        var resolvedEndLocation: LocationInput?

        switch planningMode {
        case .dateRange:
            // Use provided date range, no location needed
            // Games will be found within the date range across all regions
            effectiveStartDate = startDate
            effectiveEndDate = endDate

        case .gameFirst:
            // Calculate date range from selected games + buffer
            if let dateRange = gameFirstDateRange {
                effectiveStartDate = dateRange.start
                effectiveEndDate = dateRange.end
            }
            // Derive start/end locations from first/last game stadiums
            if let firstGame = selectedGames.sorted(by: { $0.game.dateTime < $1.game.dateTime }).first,
               let lastGame = selectedGames.sorted(by: { $0.game.dateTime < $1.game.dateTime }).last {
                resolvedStartLocation = LocationInput(
                    name: firstGame.stadium.city,
                    coordinate: firstGame.stadium.coordinate,
                    address: "\(firstGame.stadium.city), \(firstGame.stadium.state)"
                )
                resolvedEndLocation = LocationInput(
                    name: lastGame.stadium.city,
                    coordinate: lastGame.stadium.coordinate,
                    address: "\(lastGame.stadium.city), \(lastGame.stadium.state)"
                )
            }

        case .locations:
            // Resolve provided locations
            print("🔍 ViewModel.planTrip: .locations mode")
            print("   - startLocationText: '\(startLocationText)'")
            print("   - endLocationText: '\(endLocationText)'")
            print("   - startLocation BEFORE resolve: \(startLocation?.name ?? "nil")")
            print("   - endLocation BEFORE resolve: \(endLocation?.name ?? "nil")")

            await resolveLocations()
            resolvedStartLocation = startLocation
            resolvedEndLocation = endLocation

            print("   - startLocation AFTER resolve: \(startLocation?.name ?? "nil")")
            print("   - endLocation AFTER resolve: \(endLocation?.name ?? "nil")")
            print("   - resolvedStartLocation: \(resolvedStartLocation?.name ?? "nil")")
            print("   - resolvedEndLocation: \(resolvedEndLocation?.name ?? "nil")")

            guard resolvedStartLocation != nil, resolvedEndLocation != nil else {
                viewState = .error("Could not resolve start or end location")
                return
            }

        case .followTeam:
            // Use provided date range
            effectiveStartDate = startDate
            effectiveEndDate = endDate

            // If using home location, resolve it
            if useHomeLocation && !startLocationText.isEmpty {
                await resolveLocations()
                resolvedStartLocation = startLocation
                resolvedEndLocation = startLocation  // Round trip - same start/end
            }
            // Otherwise, planner will use first/last game locations (fly-in/fly-out)
        }

        // Ensure we have games data
        if games.isEmpty {
            await loadScheduleData()
        }

        // Build preferences
        let preferences = TripPreferences(
            planningMode: planningMode,
            startLocation: resolvedStartLocation,
            endLocation: resolvedEndLocation,
            sports: selectedSports,
            mustSeeGameIds: mustSeeGameIds,
            travelMode: travelMode,
            startDate: effectiveStartDate,
            endDate: effectiveEndDate,
            numberOfStops: useStopCount ? numberOfStops : nil,
            tripDuration: useStopCount ? nil : tripDurationDays,
            leisureLevel: leisureLevel,
            mustStopLocations: mustStopLocations,
            preferredCities: preferredCities,
            routePreference: routePreference,
            needsEVCharging: needsEVCharging,
            lodgingType: lodgingType,
            numberOfDrivers: numberOfDrivers,
            maxDrivingHoursPerDriver: maxDrivingHoursPerDriver,
            allowRepeatCities: allowRepeatCities,
            selectedRegions: selectedRegions,
            followTeamId: followTeamId,
            useHomeLocation: useHomeLocation,
            gameFirstTripDuration: gameFirstTripDuration
        )

        // Build planning request
        let request = PlanningRequest(
            preferences: preferences,
            availableGames: games,
            teams: teams,
            stadiums: stadiums
        )

        // Plan the trip
        let result = planningEngine.planItineraries(request: request)

        switch result {
        case .success(var options):
            guard !options.isEmpty else {
                viewState = .error("No valid itinerary found")
                return
            }

            // Enrich with EV chargers if requested and feature is enabled
            if FeatureFlags.enableEVCharging && needsEVCharging {
                options = await ItineraryBuilder.enrichWithEVChargers(options)
            }

            // Store preferences for later conversion
            currentPreferences = preferences

            if options.count == 1 {
                // Only one option - go directly to detail
                let trip = convertToTrip(option: options[0], preferences: preferences)
                viewState = .completed(trip)
            } else {
                // Multiple options - show selection view
                viewState = .selectingOption(options)
            }

        case .failure(let failure):
            viewState = .error(failureMessage(for: failure))
        }
    }

    func toggleMustSeeGame(_ gameId: String) {
        if mustSeeGameIds.contains(gameId) {
            mustSeeGameIds.remove(gameId)
        } else {
            mustSeeGameIds.insert(gameId)
        }
    }

    func deselectAllGames() {
        mustSeeGameIds.removeAll()
    }

    func switchPlanningMode(_ mode: PlanningMode) {
        // Just set the mode - didSet observer handles state reset
        planningMode = mode
    }

    /// Load games for browsing in game-first mode
    func loadGamesForBrowsing() async {
        isLoadingGames = true
        do {
            // Ensure initial data is loaded
            if dataProvider.teams.isEmpty {
                await dataProvider.loadInitialData()
            }

            // Use cached teams and stadiums from data provider
            for team in dataProvider.teams {
                teams[team.id] = team
            }
            for stadium in dataProvider.stadiums {
                stadiums[stadium.id] = stadium
            }

            // Fetch all games for browsing (no date filter)
            games = try await dataProvider.allGames(for: selectedSports)

            // Build rich games for display
            availableGames = games.compactMap { game -> RichGame? in
                guard let homeTeam = teams[game.homeTeamId],
                      let awayTeam = teams[game.awayTeamId],
                      let stadium = stadiums[game.stadiumId] else {
                    return nil
                }
                return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
            }.sorted { $0.game.dateTime < $1.game.dateTime }

        } catch {
            viewState = .error("Failed to load games: \(error.localizedDescription)")
        }
        isLoadingGames = false
    }

    func addMustStopLocation(_ location: LocationInput) {
        guard !mustStopLocations.contains(where: { $0.name == location.name }) else { return }
        mustStopLocations.append(location)
    }

    func removeMustStopLocation(_ location: LocationInput) {
        mustStopLocations.removeAll { $0.name == location.name }
    }

    func addPreferredCity(_ city: String) {
        guard !city.isEmpty, !preferredCities.contains(city) else { return }
        preferredCities.append(city)
    }

    func removePreferredCity(_ city: String) {
        preferredCities.removeAll { $0 == city }
    }

    func reset() {
        viewState = .editing
        planningMode = .dateRange
        startLocationText = ""
        endLocationText = ""
        startLocation = nil
        endLocation = nil
        selectedSports = [.mlb]
        startDate = Date()
        endDate = Date().addingTimeInterval(86400 * 7)
        mustSeeGameIds = []
        numberOfStops = 5
        leisureLevel = .moderate
        mustStopLocations = []
        preferredCities = []
        availableGames = []
        isLoadingGames = false
        currentPreferences = nil
        allowRepeatCities = true
        selectedRegions = [.east, .central, .west]
    }

    /// Toggles region selection. Any combination is allowed.
    func toggleRegion(_ region: Region) {
        if selectedRegions.contains(region) {
            selectedRegions.remove(region)
        } else {
            selectedRegions.insert(region)
        }
    }

    /// Select a specific itinerary option and navigate to its detail
    func selectOption(_ option: ItineraryOption) {
        guard let preferences = currentPreferences else {
            viewState = .error("Unable to load trip preferences")
            return
        }
        let trip = convertToTrip(option: option, preferences: preferences)
        viewState = .completed(trip)
    }

    /// Convert an itinerary option to a Trip (public for use by TripOptionsView)
    func convertOptionToTrip(_ option: ItineraryOption) -> Trip {
        let preferences = currentPreferences ?? TripPreferences(
            planningMode: planningMode,
            startLocation: nil,
            endLocation: nil,
            sports: selectedSports,
            mustSeeGameIds: mustSeeGameIds,
            travelMode: travelMode,
            startDate: startDate,
            endDate: endDate,
            numberOfStops: useStopCount ? numberOfStops : nil,
            tripDuration: useStopCount ? nil : tripDurationDays,
            leisureLevel: leisureLevel,
            mustStopLocations: mustStopLocations,
            preferredCities: preferredCities,
            routePreference: routePreference,
            needsEVCharging: needsEVCharging,
            lodgingType: lodgingType,
            numberOfDrivers: numberOfDrivers,
            maxDrivingHoursPerDriver: maxDrivingHoursPerDriver,
            allowRepeatCities: allowRepeatCities,
            selectedRegions: selectedRegions,
            followTeamId: followTeamId,
            useHomeLocation: useHomeLocation,
            gameFirstTripDuration: gameFirstTripDuration
        )
        return convertToTrip(option: option, preferences: preferences)
    }

    /// Go back to option selection from trip detail
    func backToOptions() {
        if case .completed = viewState {
            // We'd need to store options to go back - for now, restart planning
            viewState = .editing
        }
    }

    // MARK: - Conversion Helpers

    private func convertToTrip(option: ItineraryOption, preferences: TripPreferences) -> Trip {
        // Convert ItineraryStops to TripStops
        let tripStops = option.stops.enumerated().map { index, stop in
            TripStop(
                stopNumber: index + 1,
                city: stop.city,
                state: stop.state,
                coordinate: stop.coordinate,
                arrivalDate: stop.arrivalDate,
                departureDate: stop.departureDate,
                games: stop.games,
                isRestDay: stop.games.isEmpty
            )
        }

        return Trip(
            name: generateTripName(from: tripStops),
            preferences: preferences,
            stops: tripStops,
            travelSegments: option.travelSegments,
            totalGames: option.totalGames,
            totalDistanceMeters: option.totalDistanceMiles * 1609.34,
            totalDrivingSeconds: option.totalDrivingHours * 3600
        )
    }

    private func generateTripName(from stops: [TripStop]) -> String {
        let cities = stops.compactMap { $0.city }.prefix(3)
        if cities.count <= 1 {
            return cities.first ?? "Road Trip"
        }
        return cities.joined(separator: " → ")
    }

    private func failureMessage(for failure: PlanningFailure) -> String {
        failure.message
    }
}
