import XCTest

final class ScreensUITests: XCTestCase {
    override func setUp() {
        continueAfterFailure = false
    }

    @MainActor
    func testLayoutFillsScreenAndCoreFlows() {
        let app = XCUIApplication()
        app.launch()

        // ---- Top chrome is present and flush with the safe area top.
        let title = app.staticTexts["Screens"]
        XCTAssertTrue(title.waitForExistence(timeout: 5), "Title should appear")

        let screenFrame = app.windows.firstMatch.frame
        let titleY = title.frame.midY
        XCTAssertLessThan(titleY, screenFrame.height * 0.25,
                          "Title should sit in the top 25% of the screen; actual Y \(titleY) in screen \(screenFrame.height)")

        // ---- Tap + to open Add Connection sheet.
        let addButton = app.buttons["Add connection"]
        XCTAssertTrue(addButton.exists, "Add button should exist")
        addButton.tap()

        let displayNameField = app.textFields["Display name"]
        XCTAssertTrue(displayNameField.waitForExistence(timeout: 2),
                      "Add Connection sheet should present and show Display name field")

        app.buttons["Cancel"].tap()
        XCTAssertFalse(displayNameField.waitForExistence(timeout: 1),
                       "Add sheet should dismiss on Cancel")

        // ---- Settings gear opens Settings sheet.
        app.buttons["Settings"].tap()
        let settingsTitle = app.navigationBars.staticTexts["Settings"]
        XCTAssertTrue(settingsTitle.waitForExistence(timeout: 2),
                      "Settings sheet should present")
        app.buttons["Done"].tap()

        // ---- Search field accepts input and clears.
        let search = app.textFields.matching(NSPredicate(format: "placeholderValue == %@", "Search connections")).firstMatch
        XCTAssertTrue(search.waitForExistence(timeout: 2), "Search field should exist")
        search.tap()
        search.typeText("mini")
        XCTAssertEqual(search.value as? String, "mini",
                       "Search text should round-trip")
        XCTAssertTrue(title.isHittable, "Title should remain on screen during search")

        // ---- Empty-state CTA routes to Add Connection.
        let emptyCTA = app.buttons["Add a computer"]
        if emptyCTA.waitForExistence(timeout: 1) {
            app.buttons["Clear search"].tap()
            emptyCTA.tap()
            XCTAssertTrue(displayNameField.waitForExistence(timeout: 2),
                          "Empty-state CTA should present Add Connection sheet")
            app.buttons["Cancel"].tap()
        }
    }

    /// Adds a connection with an unreachable host so the SessionView opens
    /// (controller exists, no real network needed) and verifies that tapping
    /// the keyboard icon presents the iOS keyboard and that pressing keys
    /// flows through the framebuffer view's input handling.
    @MainActor
    func testSoftwareKeyboardSendsCharactersToFramebuffer() {
        let app = XCUIApplication()
        app.launch()

        // Add a bogus connection
        app.buttons["Add connection"].tap()
        let nameField = app.textFields["Display name"]
        XCTAssertTrue(nameField.waitForExistence(timeout: 2))
        nameField.tap()
        nameField.typeText("KeyboardTest")
        let hostField = app.textFields["Host or IP"]
        hostField.tap()
        hostField.typeText("127.0.0.1")
        app.buttons["Add"].tap()

        // Open the connection
        app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "KeyboardTest")).firstMatch.tap()

        // Framebuffer exists (as a TextView once UIKeyInput is adopted)
        let framebuffer = app.descendants(matching: .any)
            .matching(identifier: "framebuffer").firstMatch
        XCTAssertTrue(framebuffer.waitForExistence(timeout: 5),
                      "Framebuffer view should exist in session")

        // The diagnostic probe is a hidden sibling element that records
        // keyboard plumbing events via its accessibilityLabel.
        let diag = app.descendants(matching: .any)
            .matching(identifier: "fb-diag").firstMatch
        XCTAssertTrue(diag.waitForExistence(timeout: 3),
                      "Diagnostic probe should exist")

        // State probe
        let state = app.descendants(matching: .any)
            .matching(identifier: "sessionview-state").firstMatch
        XCTAssertTrue(state.waitForExistence(timeout: 3))
        XCTAssertEqual(state.label, "kb=false", "Initial binding should be false")

        // Tap the keyboard toggle in the toolbar
        let kbToggle = app.buttons["Toggle keyboard bar"]
        XCTAssertTrue(kbToggle.waitForExistence(timeout: 2))
        kbToggle.tap()

        // Verify the binding flipped
        let flippedTrue = NSPredicate(format: "label == %@", "kb=true")
        wait(for: [expectation(for: flippedTrue, evaluatedWith: state)], timeout: 3)

        // Wait for the diagnostic to record that we asked for the keyboard.
        // The framebuffer was already first responder (for hardware keys), so
        // we hit the reload path rather than `became:true`, which is fine.
        let askedForKeyboard = NSPredicate(format: "label CONTAINS %@", "[set:true]")
        wait(for: [expectation(for: askedForKeyboard, evaluatedWith: diag)],
             timeout: 3)

        // Force the on-screen keyboard to appear (simulator suppresses it
        // when Connect Hardware Keyboard is on).
        // The simulator can be told to disconnect the host keyboard via the
        // hardware menu; we instead just wait briefly and then attempt taps.
        Thread.sleep(forTimeInterval: 0.5)

        // If the system keyboard is visible, drive it like a user. Otherwise
        // fall back to typeText (which bypasses the UI keyboard).
        let kb = app.keyboards.firstMatch
        if kb.waitForExistence(timeout: 1) {
            kb.keys["h"].tap()
            kb.keys["i"].tap()
        } else {
            app.typeText("hi")
        }

        let typedH = NSPredicate(format: "label CONTAINS %@", "[ins:h]")
        wait(for: [expectation(for: typedH, evaluatedWith: diag)], timeout: 3)
        let typedI = NSPredicate(format: "label CONTAINS %@", "[ins:i]")
        wait(for: [expectation(for: typedI, evaluatedWith: diag)], timeout: 3)
    }
}
