//
//  ItineraryTravelConstraintTests.swift
//  SportsTimeTests
//
//  Tests for travel segment movement constraints.
//

import XCTest
@testable import SportsTime

private typealias H = ItineraryTestHelpers

final class ItineraryTravelConstraintTests: XCTestCase {

    private let testTripId = H.testTripId
    private let testDate = H.testDate

    // MARK: - Travel Constraint Tests

    func test_travel_cannotMoveBeforeLastDepartureGame() {
        // Given: Chicago has games on Days 1-2, Detroit has game on Day 4
        // Travel Chicago → Detroit valid range: Days 2-4
        // Travel cannot be on Day 1 (before last Chicago game)

        let chicagoGame1 = H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let chicagoGame2 = H.makeGameItem(city: "Chicago", day: 2, sortOrder: 100)
        let detroitGame = H.makeGameItem(city: "Detroit", day: 4, sortOrder: 100)

        let constraints = ItineraryConstraints(
            tripDayCount: 5,
            items: [chicagoGame1, chicagoGame2, detroitGame]
        )

        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50)

        // Travel on Day 1 should be INVALID (Chicago game on Day 2 not finished)
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: 50), "Travel on Day 1 should be invalid - must wait for Day 2 Chicago game")

        // Travel on Day 2 after the game should be VALID
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 2, sortOrder: 150), "Travel on Day 2 after game should be valid")
    }

    func test_travel_cannotMoveAfterFirstArrivalGame() {
        // Given: Detroit has games on Days 3-4
        // Travel to Detroit must arrive by Day 3 (before first game)

        let chicagoGame = H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let detroitGame1 = H.makeGameItem(city: "Detroit", day: 3, sortOrder: 100)
        let detroitGame2 = H.makeGameItem(city: "Detroit", day: 4, sortOrder: 100)

        let constraints = ItineraryConstraints(
            tripDayCount: 5,
            items: [chicagoGame, detroitGame1, detroitGame2]
        )

        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 4, sortOrder: 50)

        // Travel on Day 4 should be INVALID (missed Day 3 Detroit game)
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 4, sortOrder: 50), "Travel on Day 4 should be invalid - missed Day 3 game")

        // Travel on Day 3 before the game should be VALID
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: 50), "Travel on Day 3 before game should be valid")
    }

    func test_travel_onEdgeDay_mustRespectSortOrderConstraints() {
        // Given: Chicago game on Day 1 at sortOrder 100
        // Travel on Day 1 must have sortOrder > 100 (after the game)

        let chicagoGame = H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let constraints = ItineraryConstraints(tripDayCount: 3, items: [chicagoGame])
        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50)

        // SortOrder 50 is BEFORE the game - INVALID
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: 50), "Travel before game on same day should be invalid")

        // SortOrder 100 is AT the game - INVALID (must be strictly after)
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: 100), "Travel at same sortOrder as game should be invalid")

        // SortOrder 150 is AFTER the game - VALID
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 1, sortOrder: 150), "Travel after game on same day should be valid")
    }

    func test_travel_onArrivalDay_mustBeBeforeGame() {
        // Given: Detroit game on Day 3 at sortOrder 100
        // Travel arriving Day 3 must have sortOrder < 100 (before the game)

        let detroitGame = H.makeGameItem(city: "Detroit", day: 3, sortOrder: 100)
        let constraints = ItineraryConstraints(tripDayCount: 3, items: [detroitGame])
        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 150)

        // SortOrder 150 is AFTER the game - INVALID
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 3, sortOrder: 150), "Travel after game on arrival day should be invalid")

        // SortOrder 100 is AT the game - INVALID
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 3, sortOrder: 100), "Travel at same sortOrder as arrival game should be invalid")

        // SortOrder 50 is BEFORE the game - VALID
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: 50), "Travel before game on arrival day should be valid")
    }

    func test_travel_validDayRange_calculatedCorrectly() {
        // Given: Chicago games Days 1-2, Detroit games Days 4-5
        // Travel valid range should be Days 2-4

        let games = [
            H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100),
            H.makeGameItem(city: "Chicago", day: 2, sortOrder: 100),
            H.makeGameItem(city: "Detroit", day: 4, sortOrder: 100),
            H.makeGameItem(city: "Detroit", day: 5, sortOrder: 100)
        ]

        let constraints = ItineraryConstraints(tripDayCount: 6, items: games)
        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 50)

        let range = constraints.validDayRange(for: travel)
        XCTAssertEqual(range, 2...4, "Valid range should be Days 2-4")
    }

    func test_travel_impossibleConstraints_returnsNil() {
        // Given: Chicago game on Day 3, Detroit game on Day 1
        // This is impossible - can't leave after Day 3 and arrive by Day 1

        let games = [
            H.makeGameItem(city: "Chicago", day: 3, sortOrder: 100),
            H.makeGameItem(city: "Detroit", day: 1, sortOrder: 100)
        ]

        let constraints = ItineraryConstraints(tripDayCount: 3, items: games)
        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 50)

        let range = constraints.validDayRange(for: travel)
        XCTAssertNil(range, "Impossible constraints should return nil range")
    }

    // MARK: - Barrier Games Tests

    func test_barrierGames_identifiesCorrectGames() {
        // Given: Chicago games Days 1-2, Detroit games Days 4-5
        // Barriers should be: last Chicago game (Day 2) and first Detroit game (Day 4)

        let chicagoGame1 = H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let chicagoGame2 = H.makeGameItem(city: "Chicago", day: 2, sortOrder: 100)
        let detroitGame1 = H.makeGameItem(city: "Detroit", day: 4, sortOrder: 100)
        let detroitGame2 = H.makeGameItem(city: "Detroit", day: 5, sortOrder: 100)

        let constraints = ItineraryConstraints(
            tripDayCount: 6,
            items: [chicagoGame1, chicagoGame2, detroitGame1, detroitGame2]
        )

        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 50)

        let barriers = constraints.barrierGames(for: travel)

        XCTAssertEqual(barriers.count, 2, "Should identify 2 barrier games")
        XCTAssertTrue(barriers.contains { $0.id == chicagoGame2.id }, "Should include last Chicago game")
        XCTAssertTrue(barriers.contains { $0.id == detroitGame1.id }, "Should include first Detroit game")
    }

    // MARK: - Travel with Games on Same Day Tests

    func test_travel_departureDay_sortOrderMustBeAfterLastGame() {
        // Given: Chicago game at sortOrder 100, travel from Chicago on same day
        // Travel sortOrder must be > 100

        let chicagoGame = H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let travel = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 150)

        let dayData = ItineraryDayData(
            id: 1,
            dayNumber: 1,
            date: testDate,
            games: [H.makeRichGame(city: "Chicago", hour: 19)],
            items: [.travel(H.makeTravelSegment(from: "Chicago", to: "Detroit"), dayNumber: 1)],
            travelBefore: nil
        )

        let controller = ItineraryTableViewController(style: .plain)
        var capturedSortOrder: Double = 0
        controller.onTravelMoved = { _, _, sortOrder in
            capturedSortOrder = sortOrder
        }
        controller.reloadData(
            days: [dayData],
            travelValidRanges: ["travel:0:chicago->detroit": 1...1],
            itineraryItems: [chicagoGame, travel]
        )

        // Rows: 0=header, 1=games, 2=travel
        // Travel is already at valid position, just verify it stays after games
        XCTAssertTrue(controller.tableView(controller.tableView, canMoveRowAt: IndexPath(row: 2, section: 0)))
    }

    // MARK: - Travel Movement Tests

    func test_travel_moveToValidDay_callsCallback() {
        // Given: Travel with valid range 2-3
        let travel = H.makeTravelSegment(from: "Chicago", to: "Detroit")
        let travelItem = ItineraryRowItem.travel(travel, dayNumber: 2)

        // Travel model item for sortOrder lookup
        let travelModelItem = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 1.0)

        let day1 = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [], travelBefore: nil)
        let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [travelItem], travelBefore: nil)
        let day3 = ItineraryDayData(id: 3, dayNumber: 3, date: H.dayAfter(H.dayAfter(testDate)), games: [], items: [], travelBefore: nil)

        var capturedTravelId: String = ""
        var capturedDay: Int = 0
        let controller = ItineraryTableViewController(style: .plain)
        controller.onTravelMoved = { travelId, day, _ in
            capturedTravelId = travelId
            capturedDay = day
        }
        controller.reloadData(
            days: [day1, day2, day3],
            travelValidRanges: ["travel:0:chicago->detroit": 2...3],
            itineraryItems: [travelModelItem]
        )

        // Rows: 0=Day1 header, 1=Day2 header, 2=travel, 3=Day3 header
        // Move travel (row 2) to row 3 (after Day3 header = Day 3)
        controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 2, section: 0), to: IndexPath(row: 3, section: 0))

        XCTAssertEqual(capturedTravelId, "travel:0:chicago->detroit")
        XCTAssertEqual(capturedDay, 3, "Travel should now be on Day 3")
    }

    // MARK: - Move Validation Tests

    func test_moveValidation_travel_snapsToValidDayRange() {
        // Given: Travel with valid range Days 2-3
        let travel = H.makeTravelSegment(from: "Chicago", to: "Detroit")
        let travelId = "travel:0:chicago->detroit"
        let travelItem = ItineraryRowItem.travel(travel, dayNumber: 2)
        let travelModelItem = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 1.0)

        let day1 = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [], travelBefore: nil)
        let day2 = ItineraryDayData(id: 2, dayNumber: 2, date: H.dayAfter(testDate), games: [], items: [travelItem], travelBefore: nil)
        let day3 = ItineraryDayData(id: 3, dayNumber: 3, date: H.dayAfter(H.dayAfter(testDate)), games: [], items: [], travelBefore: nil)

        let controller = ItineraryTableViewController(style: .plain)
        let validRanges = [travelId: 2...3]
        controller.reloadData(days: [day1, day2, day3], travelValidRanges: validRanges, itineraryItems: [travelModelItem])

        // Rows: 0=Day1 header, 1=Day2 header, 2=travel, 3=Day3 header
        // Travel is at row 2 (after Day2 header at row 1)
        // Try to move it to Day 1 area (row 0) - should snap back to valid range
        let source = IndexPath(row: 2, section: 0)
        let proposed = IndexPath(row: 0, section: 0)

        let result = controller.tableView(controller.tableView, targetIndexPathForMoveFromRowAt: source, toProposedIndexPath: proposed)

        // Result should NOT be row 0 (Day 1 is outside valid range)
        XCTAssertGreaterThan(result.row, 0, "Travel should snap away from invalid Day 1")
    }

    // MARK: - Complex Scenario

    func test_complexScenario_multiCityTripWithConstraints() {
        // Given: A 7-day trip with:
        // - Chicago games Days 1-2
        // - Travel Chicago → Detroit (valid Days 2-4)
        // - Detroit games Days 4-5
        // - Travel Detroit → Milwaukee (valid Days 5-6)
        // - Milwaukee game Day 6

        let chicagoGame1 = H.makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let chicagoGame2 = H.makeGameItem(city: "Chicago", day: 2, sortOrder: 100)
        let detroitGame1 = H.makeGameItem(city: "Detroit", day: 4, sortOrder: 100)
        let detroitGame2 = H.makeGameItem(city: "Detroit", day: 5, sortOrder: 100)
        let milwaukeeGame = H.makeGameItem(city: "Milwaukee", day: 6, sortOrder: 100)

        let constraints = ItineraryConstraints(
            tripDayCount: 7,
            items: [chicagoGame1, chicagoGame2, detroitGame1, detroitGame2, milwaukeeGame]
        )

        // Travel 1: Chicago → Detroit
        let travel1 = H.makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 50)
        XCTAssertEqual(constraints.validDayRange(for: travel1), 2...4)

        // Travel 2: Detroit → Milwaukee
        let travel2 = H.makeTravelItem(from: "Detroit", to: "Milwaukee", day: 5, sortOrder: 150)
        XCTAssertEqual(constraints.validDayRange(for: travel2), 5...6)

        // Invalid positions
        XCTAssertFalse(constraints.isValidPosition(for: travel1, day: 1, sortOrder: 50), "Travel1 on Day 1 invalid")
        XCTAssertFalse(constraints.isValidPosition(for: travel1, day: 5, sortOrder: 50), "Travel1 on Day 5 invalid")

        XCTAssertFalse(constraints.isValidPosition(for: travel2, day: 4, sortOrder: 50), "Travel2 on Day 4 invalid")
        XCTAssertFalse(constraints.isValidPosition(for: travel2, day: 7, sortOrder: 50), "Travel2 on Day 7 invalid")
    }
}
