//
//  RouteFilters.swift
//  SportsTime
//
//  Filters itinerary results based on user preferences.
//  Applied in TripPlanningEngine AFTER scenario planners return.
//

import Foundation
import CoreLocation

/// Filters for itinerary options and trips.
///
/// Provides post-planning filtering to enforce user preferences like
/// repeat city rules, sport filtering, and date range filtering.
///
/// - Invariants:
///   - Filtering is idempotent: filter(filter(x)) == filter(x)
///   - Empty input → empty output (preservation)
///   - Filtering never adds items, only removes
///   - Filter order doesn't matter for same criteria
enum RouteFilters {

    // MARK: - Repeat Cities Filter

    /// Filters itinerary options based on repeat city rules.
    ///
    /// - Parameters:
    ///   - options: Itinerary options to filter
    ///   - allow: If true, returns all options; if false, removes options visiting any city on multiple days
    /// - Returns: Filtered options
    ///
    /// - Expected Behavior:
    ///   - allow=true → returns all options unchanged
    ///   - allow=false → removes options where any city is visited on different calendar days
    ///   - Empty options → returns empty array
    ///   - Same city on same day (multiple stops) is allowed
    static func filterRepeatCities(
        _ options: [ItineraryOption],
        allow: Bool
    ) -> [ItineraryOption] {
        guard !allow else { return options }
        return options.filter { !hasRepeatCityViolation($0) }
    }

    /// Checks if an itinerary visits any city on multiple calendar days.
    ///
    /// - Parameter option: Itinerary option to check
    /// - Returns: true if any city appears on more than one distinct calendar day
    ///
    /// - Expected Behavior:
    ///   - Single stop → false
    ///   - Multiple stops, different cities → false
    ///   - Same city, same day → false (allowed)
    ///   - Same city, different days → true (violation)
    static func hasRepeatCityViolation(_ option: ItineraryOption) -> Bool {
        let calendar = Calendar.current
        var cityDays: [String: Set<Date>] = [:]

        for stop in option.stops {
            let city = stop.city
            let day = calendar.startOfDay(for: stop.arrivalDate)
            cityDays[city, default: []].insert(day)
        }

        // Violation if any city has more than 1 day
        return cityDays.values.contains(where: { $0.count > 1 })
    }

    /// Gets cities that are visited on multiple days across all options.
    ///
    /// - Parameter options: Itinerary options to analyze
    /// - Returns: Sorted array of city names that appear on multiple days
    ///
    /// - Expected Behavior:
    ///   - Empty options → empty array
    ///   - No violations → empty array
    ///   - Returns unique city names, alphabetically sorted
    ///   - Aggregates across all options
    static func findRepeatCities(in options: [ItineraryOption]) -> [String] {
        var violatingCities = Set<String>()
        let calendar = Calendar.current

        for option in options {
            var cityDays: [String: Set<Date>] = [:]
            for stop in option.stops {
                let day = calendar.startOfDay(for: stop.arrivalDate)
                cityDays[stop.city, default: []].insert(day)
            }
            for (city, days) in cityDays where days.count > 1 {
                violatingCities.insert(city)
            }
        }

        return Array(violatingCities).sorted()
    }

    // MARK: - Trip List Filters

    /// Filters trips by sport.
    ///
    /// - Parameters:
    ///   - trips: Trips to filter
    ///   - sports: Sports to match
    /// - Returns: Trips containing ANY of the specified sports
    ///
    /// - Expected Behavior:
    ///   - Empty sports set → returns all trips unchanged
    ///   - Trip with matching sport → included
    ///   - Trip with none of the sports → excluded
    ///   - Trip with multiple sports, one matches → included
    static func filterBySport(_ trips: [Trip], sports: Set<Sport>) -> [Trip] {
        guard !sports.isEmpty else { return trips }
        return trips.filter { trip in
            !trip.preferences.sports.isDisjoint(with: sports)
        }
    }

    /// Filters trips by date range overlap.
    ///
    /// - Parameters:
    ///   - trips: Trips to filter
    ///   - start: Range start date
    ///   - end: Range end date
    /// - Returns: Trips that overlap with the specified range
    ///
    /// - Expected Behavior:
    ///   - Trip fully inside range → included
    ///   - Trip partially overlapping → included
    ///   - Trip fully outside range → excluded
    ///   - Trip ending on range start → included (same day counts as overlap)
    ///   - Trip starting on range end → included (same day counts as overlap)
    ///   - Uses calendar start-of-day for comparison
    static func filterByDateRange(_ trips: [Trip], start: Date, end: Date) -> [Trip] {
        let calendar = Calendar.current
        let rangeStart = calendar.startOfDay(for: start)
        let rangeEnd = calendar.startOfDay(for: end)

        return trips.filter { trip in
            let tripStart = calendar.startOfDay(for: trip.startDate)
            let tripEnd = calendar.startOfDay(for: trip.endDate)

            // Trip overlaps if it starts before range ends AND ends after range starts
            return tripStart <= rangeEnd && tripEnd >= rangeStart
        }
    }

    /// Filters trips by status.
    ///
    /// - Parameters:
    ///   - trips: Trips to filter
    ///   - status: Status to match
    /// - Returns: Trips with matching status
    ///
    /// - Expected Behavior:
    ///   - Returns only trips where trip.status == status
    ///   - Empty trips → returns empty array
    static func filterByStatus(_ trips: [Trip], status: TripStatus) -> [Trip] {
        trips.filter { $0.status == status }
    }

    /// Applies multiple filters in sequence.
    ///
    /// - Parameters:
    ///   - trips: Trips to filter
    ///   - sports: Optional sports filter (nil = no filter)
    ///   - dateRange: Optional date range filter (nil = no filter)
    ///   - status: Optional status filter (nil = no filter)
    /// - Returns: Trips matching ALL specified criteria (intersection)
    ///
    /// - Expected Behavior:
    ///   - nil criteria → skip that filter
    ///   - Empty sports set → skip sport filter
    ///   - Order of filters doesn't affect result
    ///   - Result is intersection (AND) of all criteria
    static func applyFilters(
        _ trips: [Trip],
        sports: Set<Sport>? = nil,
        dateRange: (start: Date, end: Date)? = nil,
        status: TripStatus? = nil
    ) -> [Trip] {
        var result = trips

        if let sports = sports, !sports.isEmpty {
            result = filterBySport(result, sports: sports)
        }

        if let range = dateRange {
            result = filterByDateRange(result, start: range.start, end: range.end)
        }

        if let status = status {
            result = filterByStatus(result, status: status)
        }

        return result
    }

}
