import XCTest

final class NotificationsUITests: ForgejoUITestBase {

    // MARK: - Notifications (unread, filter, swipe mark-read)

    @MainActor
    func testNotifications() throws {
        loginAndWaitForHome()
        app.tabBars.buttons["Notifications"].tap()

        let notificationsList = app.collectionViews.firstMatch
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
        XCTAssertTrue(app.staticTexts["testadmin/test-repo"].waitForExistence(timeout: 5))

        // Filter by Read
        let readButton = app.buttons["Read"]
        XCTAssertTrue(readButton.waitForExistence(timeout: 5))
        readButton.tap()

        // Switch to All
        let allButton = app.buttons["All"]
        XCTAssertTrue(allButton.waitForExistence(timeout: 5))
        allButton.tap()

        // Switch back to Unread for swipe tests
        let unreadButton = app.buttons["Unread"]
        XCTAssertTrue(unreadButton.waitForExistence(timeout: 5))
        unreadButton.tap()
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))

        // Swipe right to mark as read
        let firstCell = notificationsList.cells.firstMatch
        XCTAssertTrue(firstCell.waitForExistence(timeout: 5))
        firstCell.swipeRight()

        let markReadButton = app.buttons["Mark Read"]
        if markReadButton.waitForExistence(timeout: 3) {
            markReadButton.tap()
        }
    }

    // MARK: - Open-to-clear (#32)

    /// Opening an unread notification marks it read on the server, so it leaves
    /// the Unread filter on the next refresh.
    ///
    /// Covers the in-app NotificationsOverviewView path: the tap gesture on the
    /// row calls markReadOnOpen, which marks the thread read with
    /// removeFromUnread: false so the row stays put during navigation and only
    /// drops out of Unread on the next server fetch. The multi-instance and
    /// system-notification open paths apply the same fix but aren't reachable
    /// from this single-instance harness.
    ///
    /// Sorts after testNotifications and opens whichever unread row is first
    /// rather than a fixed one, so it's unaffected by the two notifications that
    /// test consumes (the seed leaves several more).
    @MainActor
    func testOpeningNotificationMarksItRead() throws {
        loginAndWaitForHome()
        app.tabBars.buttons["Notifications"].tap()

        let notificationsList = app.collectionViews.firstMatch
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))

        // Make sure the Unread filter is active.
        let unreadButton = app.buttons["Unread"]
        XCTAssertTrue(unreadButton.waitForExistence(timeout: 5))
        unreadButton.tap()
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))

        // Remember the first unread notification's title (the row's first text)
        // so we can assert that specific one leaves the Unread list.
        let firstCell = notificationsList.cells.firstMatch
        XCTAssertTrue(firstCell.waitForExistence(timeout: 10), "Expected at least one unread notification")
        let openedTitle = firstCell.staticTexts.firstMatch.label
        XCTAssertFalse(openedTitle.isEmpty, "Could not read the unread notification's title")

        // Open it. Navigating into the detail proves the row was tappable; the
        // nav bar title changes away from "Notifications" once it's on screen.
        firstCell.tap()
        let detailNavBar = app.navigationBars.element(boundBy: 0)
        XCTAssertTrue(detailNavBar.waitForExistence(timeout: 10), "Detail view did not open")
        XCTAssertNotEqual(detailNavBar.identifier, "Notifications", "Did not navigate into the notification detail")

        // Back to the list.
        app.navigationBars.buttons.element(boundBy: 0).tap()
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))

        // Force a server refresh of Unread by toggling the filter. Opening marks
        // the thread read but keeps the row visible until the next fetch, so this
        // round-trip is what surfaces the open-to-clear behaviour.
        app.buttons["Read"].tap()
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))
        unreadButton.tap()
        XCTAssertTrue(notificationsList.waitForExistence(timeout: 10))

        // The opened notification should no longer be under Unread.
        let openedRow = notificationsList.staticTexts[openedTitle]
        XCTAssertFalse(
            openedRow.waitForExistence(timeout: 5),
            "Opened notification \"\(openedTitle)\" should have left the Unread filter after refresh",
        )
    }
}
