import XCTest
@testable import SportsTime

final class ItineraryConstraintsTests: XCTestCase {

    // MARK: - Custom Item Tests (No Constraints)

    func test_customItem_canGoOnAnyDay() {
        // Given: A 5-day trip with games on days 1 and 5
        let constraints = makeConstraints(tripDays: 5, gameDays: [1, 5])
        let customItem = makeCustomItem(day: 1, sortOrder: 50)

        // When/Then: Custom item can go on any day
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 1, sortOrder: 50))
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 2, sortOrder: 50))
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 3, sortOrder: 50))
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 4, sortOrder: 50))
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 5, sortOrder: 50))
    }

    func test_customItem_canGoBeforeOrAfterGames() {
        // Given: A day with a game at sortOrder 100
        let constraints = makeConstraints(tripDays: 3, gameDays: [2])
        let customItem = makeCustomItem(day: 2, sortOrder: 50)

        // When/Then: Custom item can go before or after game
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 2, sortOrder: 50))  // Before
        XCTAssertTrue(constraints.isValidPosition(for: customItem, day: 2, sortOrder: 150)) // After
    }

    // MARK: - Travel Constraint Tests

    func test_travel_validDayRange_simpleCase() {
        // Given: Chicago game Day 1, Detroit game Day 3
        let constraints = makeConstraints(
            tripDays: 5,
            games: [
                makeGameItem(city: "Chicago", day: 1),
                makeGameItem(city: "Detroit", day: 3)
            ]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 200)

        // When
        let range = constraints.validDayRange(for: travel)

        // Then: Travel can be on days 1 (after game), 2, or 3 (before game)
        XCTAssertEqual(range, 1...3)
    }

    func test_travel_mustBeAfterDepartureGames() {
        // Given: Chicago game on Day 1
        // Visual boundary is 0: sortOrder < 0 = before games, sortOrder >= 0 = after games
        let constraints = makeConstraints(
            tripDays: 3,
            games: [makeGameItem(city: "Chicago", day: 1)]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: -1)

        // When/Then: Travel before game (negative sortOrder) is invalid on departure day
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: -1))

        // Travel after game (non-negative sortOrder) is valid
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 1, sortOrder: 1))
    }

    func test_travel_mustBeBeforeArrivalGames() {
        // Given: Detroit game on Day 3
        // Visual boundary is 0: sortOrder < 0 = before games, sortOrder >= 0 = after games
        let constraints = makeConstraints(
            tripDays: 3,
            games: [makeGameItem(city: "Detroit", day: 3)]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 1)

        // When/Then: Travel after arrival game (non-negative sortOrder) is invalid on arrival day
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 3, sortOrder: 1))

        // Travel before game (negative sortOrder) is valid
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: -1))
    }

    func test_travel_canBeAnywhereOnRestDays() {
        // Given: Chicago game Day 1, Detroit game Day 4, rest days 2-3
        let constraints = makeConstraints(
            tripDays: 4,
            games: [
                makeGameItem(city: "Chicago", day: 1),
                makeGameItem(city: "Detroit", day: 4)
            ]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 50)

        // When/Then: Any position on rest days is valid
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 2, sortOrder: 1))
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 2, sortOrder: 100))
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 2, sortOrder: 500))
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: 50))
    }

    func test_travel_mustBeAfterAllDepartureGamesOnSameDay() {
        // Given: Two games in Chicago on Day 1 (1pm and 7pm)
        let constraints = makeConstraints(
            tripDays: 3,
            games: [
                makeGameItem(city: "Chicago", day: 1, sortOrder: 100), // 1pm
                makeGameItem(city: "Chicago", day: 1, sortOrder: 101)  // 7pm
            ]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 100.5)

        // When/Then: Between games is invalid
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: 100.5))

        // After all games is valid
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 1, sortOrder: 150))
    }

    func test_travel_mustBeBeforeAllArrivalGamesOnSameDay() {
        // Given: Two games in Detroit on Day 3 (1pm and 7pm)
        let constraints = makeConstraints(
            tripDays: 3,
            games: [
                makeGameItem(city: "Detroit", day: 3, sortOrder: 100), // 1pm
                makeGameItem(city: "Detroit", day: 3, sortOrder: 101)  // 7pm
            ]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 100.5)

        // When/Then: Between games is invalid
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 3, sortOrder: 100.5))

        // Before all games is valid
        XCTAssertTrue(constraints.isValidPosition(for: travel, day: 3, sortOrder: 50))
    }

    func test_travel_cannotGoOutsideValidDayRange() {
        // Given: Chicago game Day 2, Detroit game Day 4
        let constraints = makeConstraints(
            tripDays: 5,
            games: [
                makeGameItem(city: "Chicago", day: 2),
                makeGameItem(city: "Detroit", day: 4)
            ]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 1, sortOrder: 50)

        // When/Then: Day 1 (before departure game) is invalid
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 1, sortOrder: 50))

        // Day 5 (after arrival game) is invalid
        XCTAssertFalse(constraints.isValidPosition(for: travel, day: 5, sortOrder: 50))
    }

    // MARK: - Game Immutability Tests

    func test_gameItem_cannotBeMoved() {
        // Given: A game item
        let gameItem = makeGameItem(city: "Chicago", day: 2)
        let constraints = makeConstraints(tripDays: 5, games: [gameItem])

        // When/Then: Game items should never be valid for any position
        XCTAssertFalse(constraints.isValidPosition(for: gameItem, day: 2, sortOrder: 100))
        XCTAssertFalse(constraints.isValidPosition(for: gameItem, day: 3, sortOrder: 100))
        XCTAssertFalse(constraints.isValidPosition(for: gameItem, day: 1, sortOrder: 50))
    }

    // MARK: - Edge Case Tests

    func test_travel_validDayRange_returnsNil_whenConstraintsImpossible() {
        // Given: Departure game on Day 3, Arrival game on Day 1 (reversed order)
        let constraints = makeConstraints(
            tripDays: 3,
            games: [
                makeGameItem(city: "Chicago", day: 3),  // Must depart AFTER day 3
                makeGameItem(city: "Detroit", day: 1)   // Must arrive BEFORE day 1
            ]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 2, sortOrder: 50)

        // When/Then: No valid range exists (impossible constraints)
        XCTAssertNil(constraints.validDayRange(for: travel))
    }

    // MARK: - Barrier Games Tests

    func test_barrierGames_returnsDepartureAndArrivalGames() {
        // Given: Chicago games Days 1-2, Detroit games Days 4-5
        let chicagoGame1 = makeGameItem(city: "Chicago", day: 1, sortOrder: 100)
        let chicagoGame2 = makeGameItem(city: "Chicago", day: 2, sortOrder: 100)
        let detroitGame1 = makeGameItem(city: "Detroit", day: 4, sortOrder: 100)
        let detroitGame2 = makeGameItem(city: "Detroit", day: 5, sortOrder: 100)

        let constraints = makeConstraints(
            tripDays: 5,
            games: [chicagoGame1, chicagoGame2, detroitGame1, detroitGame2]
        )
        let travel = makeTravelItem(from: "Chicago", to: "Detroit", day: 3, sortOrder: 50)

        // When
        let barriers = constraints.barrierGames(for: travel)

        // Then: Returns the last Chicago game and first Detroit game
        XCTAssertEqual(barriers.count, 2)
        XCTAssertTrue(barriers.contains { $0.id == chicagoGame2.id })
        XCTAssertTrue(barriers.contains { $0.id == detroitGame1.id })
    }

    // MARK: - Helpers

    private let testTripId = UUID()

    private func makeConstraints(tripDays: Int, gameDays: [Int] = []) -> ItineraryConstraints {
        let games = gameDays.map { makeGameItem(city: "TestCity", day: $0) }
        return makeConstraints(tripDays: tripDays, games: games)
    }

    private func makeConstraints(tripDays: Int, games: [ItineraryItem]) -> ItineraryConstraints {
        return ItineraryConstraints(tripDayCount: tripDays, items: games)
    }

    private func makeGameItem(city: String, day: Int, sortOrder: Double = 0) -> ItineraryItem {
        return ItineraryItem(
            tripId: testTripId,
            day: day,
            sortOrder: sortOrder,
            kind: .game(gameId: "game-\(city)-\(UUID().uuidString.prefix(4))", city: city)
        )
    }

    private func makeTravelItem(from: String, to: String, day: Int, sortOrder: Double) -> ItineraryItem {
        return ItineraryItem(
            tripId: testTripId,
            day: day,
            sortOrder: sortOrder,
            kind: .travel(TravelInfo(fromCity: from, toCity: to))
        )
    }

    private func makeCustomItem(day: Int, sortOrder: Double) -> ItineraryItem {
        return ItineraryItem(
            tripId: testTripId,
            day: day,
            sortOrder: sortOrder,
            kind: .custom(CustomInfo(title: "Test Item", icon: "star"))
        )
    }
}
