//
//  ItineraryReorderingTests.swift
//  SportsTimeTests
//
//  Tests for item reordering within and across days.
//

import XCTest
@testable import SportsTime

private typealias H = ItineraryTestHelpers

final class ItineraryReorderingTests: XCTestCase {

    private let testDate = H.testDate

    // MARK: - Same Day Reordering Tests

    func test_reorderItems_withinSameDay_preservesCorrectOrder() {
        // Given: 3 items on Day 1: A(1.0), B(2.0), C(3.0)
        // When: Move C between A and B
        // Then: New sortOrder for C should be 1.5

        let itemA = H.makeCustomItem(day: 1, sortOrder: 1.0, title: "A")
        let itemB = H.makeCustomItem(day: 1, sortOrder: 2.0, title: "B")
        let itemC = H.makeCustomItem(day: 1, sortOrder: 3.0, title: "C")

        let dayData = ItineraryDayData(
            id: 1,
            dayNumber: 1,
            date: testDate,
            games: [],
            items: [.customItem(itemA), .customItem(itemB), .customItem(itemC)],
            travelBefore: nil
        )

        var capturedSortOrder: Double = 0
        let controller = ItineraryTableViewController(style: .plain)
        controller.onCustomItemMoved = { _, _, sortOrder in
            capturedSortOrder = sortOrder
        }
        controller.reloadData(days: [dayData], travelValidRanges: [:], itineraryItems: [itemA, itemB, itemC])

        // Rows: 0=header, 1=A(1.0), 2=B(2.0), 3=C(3.0)
        // Move C (row 3) to row 2 (between A and B)
        controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 3, section: 0), to: IndexPath(row: 2, section: 0))

        XCTAssertEqual(capturedSortOrder, 1.5, accuracy: 0.01,
            "Moving C between A(1.0) and B(2.0) should give sortOrder 1.5")
    }

    func test_reorderItems_moveFirstToLast() {
        // Given: Items A(1.0), B(2.0), C(3.0)
        // When: Move A after C
        // Then: New sortOrder for A should be 4.0 (last + 1.0)

        let itemA = H.makeCustomItem(day: 1, sortOrder: 1.0, title: "A")
        let itemB = H.makeCustomItem(day: 1, sortOrder: 2.0, title: "B")
        let itemC = H.makeCustomItem(day: 1, sortOrder: 3.0, title: "C")

        let dayData = ItineraryDayData(
            id: 1,
            dayNumber: 1,
            date: testDate,
            games: [],
            items: [.customItem(itemA), .customItem(itemB), .customItem(itemC)],
            travelBefore: nil
        )

        var capturedSortOrder: Double = 0
        let controller = ItineraryTableViewController(style: .plain)
        controller.onCustomItemMoved = { _, _, sortOrder in
            capturedSortOrder = sortOrder
        }
        controller.reloadData(days: [dayData], travelValidRanges: [:], itineraryItems: [itemA, itemB, itemC])

        // Rows: 0=header, 1=A(1.0), 2=B(2.0), 3=C(3.0)
        // Move A (row 1) to row 3 (after C)
        // After removing A: 0=header, 1=B, 2=C
        // Insert at row 3: 0=header, 1=B, 2=C, 3=A
        controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 1, section: 0), to: IndexPath(row: 3, section: 0))

        XCTAssertEqual(capturedSortOrder, 4.0, accuracy: 0.01,
            "Moving A after C(3.0) should give sortOrder 4.0")
    }

    func test_reorderItems_moveLastToFirst() {
        // Given: Items A(2.0), B(4.0), C(6.0)
        // When: Move C before A
        // Then: New sortOrder for C should be 1.0 (first / 2.0)

        let itemA = H.makeCustomItem(day: 1, sortOrder: 2.0, title: "A")
        let itemB = H.makeCustomItem(day: 1, sortOrder: 4.0, title: "B")
        let itemC = H.makeCustomItem(day: 1, sortOrder: 6.0, title: "C")

        let dayData = ItineraryDayData(
            id: 1,
            dayNumber: 1,
            date: testDate,
            games: [],
            items: [.customItem(itemA), .customItem(itemB), .customItem(itemC)],
            travelBefore: nil
        )

        var capturedSortOrder: Double = 0
        let controller = ItineraryTableViewController(style: .plain)
        controller.onCustomItemMoved = { _, _, sortOrder in
            capturedSortOrder = sortOrder
        }
        controller.reloadData(days: [dayData], travelValidRanges: [:], itineraryItems: [itemA, itemB, itemC])

        // Rows: 0=header, 1=A(2.0), 2=B(4.0), 3=C(6.0)
        // Move C (row 3) to row 1 (before A, after header)
        controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 3, section: 0), to: IndexPath(row: 1, section: 0))

        XCTAssertEqual(capturedSortOrder, 1.0, accuracy: 0.01,
            "Moving C before A(2.0) should give sortOrder 1.0 (first/2)")
    }

    // MARK: - Non-Reorderable Item Tests

    func test_games_cannotBeMoved() {
        // Games should always return false for canMoveRowAt
        let games = [H.makeRichGame(city: "Detroit", hour: 19)]
        let dayData = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: games, items: [], travelBefore: nil)

        let controller = ItineraryTableViewController(style: .plain)
        controller.reloadData(days: [dayData], travelValidRanges: [:])

        // Row 0 = header, Row 1 = games
        XCTAssertFalse(controller.tableView(controller.tableView, canMoveRowAt: IndexPath(row: 1, section: 0)), "Games should not be movable")
    }

    func test_header_cannotBeMoved() {
        let dayData = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [], travelBefore: nil)

        let controller = ItineraryTableViewController(style: .plain)
        controller.reloadData(days: [dayData], travelValidRanges: [:])

        XCTAssertFalse(controller.tableView(controller.tableView, canMoveRowAt: IndexPath(row: 0, section: 0)), "Header should not be movable")
    }

    // MARK: - Callback Tests

    func test_moveHeader_doesNotCallCallback() {
        // Headers can't be moved, but verify no callback fires
        let dayData = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: [], items: [], travelBefore: nil)

        var callbackCalled = false
        let controller = ItineraryTableViewController(style: .plain)
        controller.onCustomItemMoved = { _, _, _ in
            callbackCalled = true
        }
        controller.reloadData(days: [dayData], travelValidRanges: [:])

        // Try to move header (shouldn't be possible since canMoveRowAt returns false)
        // But if someone calls moveRowAt directly:
        controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 0, section: 0), to: IndexPath(row: 0, section: 0))

        XCTAssertFalse(callbackCalled, "Moving a header should not call any callback")
    }

    func test_moveGames_doesNotCallCallback() {
        let games = [H.makeRichGame(city: "Detroit", hour: 19)]
        let dayData = ItineraryDayData(id: 1, dayNumber: 1, date: testDate, games: games, items: [], travelBefore: nil)

        var callbackCalled = false
        let controller = ItineraryTableViewController(style: .plain)
        controller.onCustomItemMoved = { _, _, _ in
            callbackCalled = true
        }
        controller.onTravelMoved = { _, _, _ in
            callbackCalled = true
        }
        controller.reloadData(days: [dayData], travelValidRanges: [:])

        // Rows: 0=header, 1=games
        // Try to move games directly (shouldn't be possible)
        controller.tableView(controller.tableView, moveRowAt: IndexPath(row: 1, section: 0), to: IndexPath(row: 0, section: 0))

        XCTAssertFalse(callbackCalled, "Moving games should not call any callback")
    }
}
