//
//  RouteFiltersTests.swift
//  SportsTimeTests
//
//  Phase 9: RouteFilters Tests
//  Filtering on All Trips list by sport, date range, and status.
//

import Testing
import Foundation
@testable import SportsTime

@Suite("RouteFilters Tests")
struct RouteFiltersTests {

    // MARK: - Test Data Helpers

    private let calendar = Calendar.current

    private func makeTrip(
        name: String = "Test Trip",
        sports: Set<Sport> = [.mlb],
        startDate: Date = Date(),
        endDate: Date? = nil,
        status: TripStatus = .planned
    ) -> Trip {
        let end = endDate ?? calendar.date(byAdding: .day, value: 7, to: startDate)!
        let preferences = TripPreferences(
            planningMode: .dateRange,
            sports: sports,
            startDate: startDate,
            endDate: end
        )

        let stop = TripStop(
            stopNumber: 1,
            city: "Test City",
            state: "TS",
            coordinate: nil,
            arrivalDate: startDate,
            departureDate: end,
            games: [UUID()],
            stadium: UUID()
        )

        return Trip(
            name: name,
            preferences: preferences,
            stops: [stop],
            status: status
        )
    }

    private func makeDate(year: Int, month: Int, day: Int) -> Date {
        calendar.date(from: DateComponents(year: year, month: month, day: day))!
    }

    // MARK: - 9.1 Filter by Single Sport

    @Test("Filter by single sport returns only matching trips")
    func test_filterBySport_SingleSport_ReturnsMatching() {
        // Arrange
        let mlbTrip = makeTrip(name: "MLB Trip", sports: [.mlb])
        let nbaTrip = makeTrip(name: "NBA Trip", sports: [.nba])
        let nhlTrip = makeTrip(name: "NHL Trip", sports: [.nhl])
        let trips = [mlbTrip, nbaTrip, nhlTrip]

        // Act
        let result = RouteFilters.filterBySport(trips, sports: [.mlb])

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "MLB Trip", "Should return the MLB trip")
    }

    // MARK: - 9.2 Filter by Multiple Sports

    @Test("Filter by multiple sports returns union of matching trips")
    func test_filterBySport_MultipleSports_ReturnsUnion() {
        // Arrange
        let mlbTrip = makeTrip(name: "MLB Trip", sports: [.mlb])
        let nbaTrip = makeTrip(name: "NBA Trip", sports: [.nba])
        let nhlTrip = makeTrip(name: "NHL Trip", sports: [.nhl])
        let multiSportTrip = makeTrip(name: "Multi Trip", sports: [.mlb, .nba])
        let trips = [mlbTrip, nbaTrip, nhlTrip, multiSportTrip]

        // Act
        let result = RouteFilters.filterBySport(trips, sports: [.mlb, .nba])

        // Assert
        #expect(result.count == 3, "Should return 3 trips (MLB, NBA, and Multi)")
        let names = Set(result.map(\.name))
        #expect(names.contains("MLB Trip"), "Should include MLB trip")
        #expect(names.contains("NBA Trip"), "Should include NBA trip")
        #expect(names.contains("Multi Trip"), "Should include multi-sport trip")
        #expect(!names.contains("NHL Trip"), "Should not include NHL trip")
    }

    // MARK: - 9.3 Filter by All Sports (Empty Filter)

    @Test("Filter with empty sports set returns all trips")
    func test_filterBySport_AllSports_ReturnsAll() {
        // Arrange
        let mlbTrip = makeTrip(name: "MLB Trip", sports: [.mlb])
        let nbaTrip = makeTrip(name: "NBA Trip", sports: [.nba])
        let nhlTrip = makeTrip(name: "NHL Trip", sports: [.nhl])
        let trips = [mlbTrip, nbaTrip, nhlTrip]

        // Act
        let result = RouteFilters.filterBySport(trips, sports: [])

        // Assert
        #expect(result.count == 3, "Empty sports filter should return all trips")
    }

    // MARK: - 9.4 Filter by Date Range

    @Test("Filter by date range returns trips within range")
    func test_filterByDateRange_ReturnsTripsInRange() {
        // Arrange
        let aprilTrip = makeTrip(
            name: "April Trip",
            startDate: makeDate(year: 2026, month: 4, day: 1),
            endDate: makeDate(year: 2026, month: 4, day: 7)
        )
        let mayTrip = makeTrip(
            name: "May Trip",
            startDate: makeDate(year: 2026, month: 5, day: 1),
            endDate: makeDate(year: 2026, month: 5, day: 7)
        )
        let juneTrip = makeTrip(
            name: "June Trip",
            startDate: makeDate(year: 2026, month: 6, day: 1),
            endDate: makeDate(year: 2026, month: 6, day: 7)
        )
        let trips = [aprilTrip, mayTrip, juneTrip]

        // Filter for May only
        let rangeStart = makeDate(year: 2026, month: 5, day: 1)
        let rangeEnd = makeDate(year: 2026, month: 5, day: 31)

        // Act
        let result = RouteFilters.filterByDateRange(trips, start: rangeStart, end: rangeEnd)

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "May Trip", "Should return the May trip")
    }

    @Test("Filter by date range includes overlapping trips")
    func test_filterByDateRange_IncludesOverlappingTrips() {
        // Arrange - Trip that spans April-May
        let spanningTrip = makeTrip(
            name: "Spanning Trip",
            startDate: makeDate(year: 2026, month: 4, day: 25),
            endDate: makeDate(year: 2026, month: 5, day: 5)
        )
        let trips = [spanningTrip]

        // Filter for just early May
        let rangeStart = makeDate(year: 2026, month: 5, day: 1)
        let rangeEnd = makeDate(year: 2026, month: 5, day: 3)

        // Act
        let result = RouteFilters.filterByDateRange(trips, start: rangeStart, end: rangeEnd)

        // Assert
        #expect(result.count == 1, "Overlapping trip should be included")
    }

    // MARK: - 9.5 Filter by Status: Planned

    @Test("Filter by planned status returns only planned trips")
    func test_filterByStatus_Planned_ReturnsPlanned() {
        // Arrange
        let plannedTrip = makeTrip(name: "Planned Trip", status: .planned)
        let inProgressTrip = makeTrip(name: "In Progress Trip", status: .inProgress)
        let completedTrip = makeTrip(name: "Completed Trip", status: .completed)
        let trips = [plannedTrip, inProgressTrip, completedTrip]

        // Act
        let result = RouteFilters.filterByStatus(trips, status: .planned)

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "Planned Trip", "Should return the planned trip")
    }

    // MARK: - 9.6 Filter by Status: In Progress

    @Test("Filter by in progress status returns only in-progress trips")
    func test_filterByStatus_InProgress_ReturnsInProgress() {
        // Arrange
        let plannedTrip = makeTrip(name: "Planned Trip", status: .planned)
        let inProgressTrip = makeTrip(name: "In Progress Trip", status: .inProgress)
        let completedTrip = makeTrip(name: "Completed Trip", status: .completed)
        let trips = [plannedTrip, inProgressTrip, completedTrip]

        // Act
        let result = RouteFilters.filterByStatus(trips, status: .inProgress)

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "In Progress Trip", "Should return the in-progress trip")
    }

    // MARK: - 9.7 Filter by Status: Completed

    @Test("Filter by completed status returns only completed trips")
    func test_filterByStatus_Completed_ReturnsCompleted() {
        // Arrange
        let plannedTrip = makeTrip(name: "Planned Trip", status: .planned)
        let inProgressTrip = makeTrip(name: "In Progress Trip", status: .inProgress)
        let completedTrip = makeTrip(name: "Completed Trip", status: .completed)
        let trips = [plannedTrip, inProgressTrip, completedTrip]

        // Act
        let result = RouteFilters.filterByStatus(trips, status: .completed)

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "Completed Trip", "Should return the completed trip")
    }

    // MARK: - 9.8 Combined Filters: Sport and Date

    @Test("Combined sport and date filters return intersection")
    func test_combinedFilters_SportAndDate_ReturnsIntersection() {
        // Arrange
        let mlbApril = makeTrip(
            name: "MLB April",
            sports: [.mlb],
            startDate: makeDate(year: 2026, month: 4, day: 1),
            endDate: makeDate(year: 2026, month: 4, day: 7)
        )
        let mlbMay = makeTrip(
            name: "MLB May",
            sports: [.mlb],
            startDate: makeDate(year: 2026, month: 5, day: 1),
            endDate: makeDate(year: 2026, month: 5, day: 7)
        )
        let nbaMay = makeTrip(
            name: "NBA May",
            sports: [.nba],
            startDate: makeDate(year: 2026, month: 5, day: 1),
            endDate: makeDate(year: 2026, month: 5, day: 7)
        )
        let trips = [mlbApril, mlbMay, nbaMay]

        // Act - Filter for MLB trips in May
        let result = RouteFilters.applyFilters(
            trips,
            sports: [.mlb],
            dateRange: (
                start: makeDate(year: 2026, month: 5, day: 1),
                end: makeDate(year: 2026, month: 5, day: 31)
            )
        )

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "MLB May", "Should return only MLB May trip")
    }

    // MARK: - 9.9 Combined Filters: All Filters

    @Test("All filters combined return intersection of all criteria")
    func test_combinedFilters_AllFilters_ReturnsIntersection() {
        // Arrange
        let matchingTrip = makeTrip(
            name: "Perfect Match",
            sports: [.mlb],
            startDate: makeDate(year: 2026, month: 5, day: 1),
            endDate: makeDate(year: 2026, month: 5, day: 7),
            status: .planned
        )
        let wrongSport = makeTrip(
            name: "Wrong Sport",
            sports: [.nba],
            startDate: makeDate(year: 2026, month: 5, day: 1),
            endDate: makeDate(year: 2026, month: 5, day: 7),
            status: .planned
        )
        let wrongDate = makeTrip(
            name: "Wrong Date",
            sports: [.mlb],
            startDate: makeDate(year: 2026, month: 4, day: 1),
            endDate: makeDate(year: 2026, month: 4, day: 7),
            status: .planned
        )
        let wrongStatus = makeTrip(
            name: "Wrong Status",
            sports: [.mlb],
            startDate: makeDate(year: 2026, month: 5, day: 1),
            endDate: makeDate(year: 2026, month: 5, day: 7),
            status: .completed
        )
        let trips = [matchingTrip, wrongSport, wrongDate, wrongStatus]

        // Act - Apply all filters
        let result = RouteFilters.applyFilters(
            trips,
            sports: [.mlb],
            dateRange: (
                start: makeDate(year: 2026, month: 5, day: 1),
                end: makeDate(year: 2026, month: 5, day: 31)
            ),
            status: .planned
        )

        // Assert
        #expect(result.count == 1, "Should return exactly 1 trip")
        #expect(result[0].name == "Perfect Match", "Should return only the perfectly matching trip")
    }

    // MARK: - 9.10 Edge Case: No Matches

    @Test("Filter with no matches returns empty array")
    func test_filter_NoMatches_ReturnsEmptyArray() {
        // Arrange
        let mlbTrip = makeTrip(name: "MLB Trip", sports: [.mlb])
        let nbaTrip = makeTrip(name: "NBA Trip", sports: [.nba])
        let trips = [mlbTrip, nbaTrip]

        // Act - Filter for NHL (none exist)
        let result = RouteFilters.filterBySport(trips, sports: [.nhl])

        // Assert
        #expect(result.isEmpty, "Should return empty array when no matches")
    }

    // MARK: - 9.11 Edge Case: All Match

    @Test("Filter where all trips match returns all trips")
    func test_filter_AllMatch_ReturnsAll() {
        // Arrange
        let trip1 = makeTrip(name: "Trip 1", status: .planned)
        let trip2 = makeTrip(name: "Trip 2", status: .planned)
        let trip3 = makeTrip(name: "Trip 3", status: .planned)
        let trips = [trip1, trip2, trip3]

        // Act
        let result = RouteFilters.filterByStatus(trips, status: .planned)

        // Assert
        #expect(result.count == 3, "Should return all 3 trips")
    }

    // MARK: - 9.12 Edge Case: Empty Input

    @Test("Filter on empty array returns empty array")
    func test_filter_EmptyInput_ReturnsEmptyArray() {
        // Arrange
        let trips: [Trip] = []

        // Act
        let resultSport = RouteFilters.filterBySport(trips, sports: [.mlb])
        let resultStatus = RouteFilters.filterByStatus(trips, status: .planned)
        let resultDate = RouteFilters.filterByDateRange(
            trips,
            start: Date(),
            end: Date().addingTimeInterval(86400 * 7)
        )
        let resultCombined = RouteFilters.applyFilters(
            trips,
            sports: [.mlb],
            dateRange: (start: Date(), end: Date()),
            status: .planned
        )

        // Assert
        #expect(resultSport.isEmpty, "filterBySport on empty should return empty")
        #expect(resultStatus.isEmpty, "filterByStatus on empty should return empty")
        #expect(resultDate.isEmpty, "filterByDateRange on empty should return empty")
        #expect(resultCombined.isEmpty, "applyFilters on empty should return empty")
    }
}
