import XCTest

struct RebuildTestUser {
    let username: String
    let email: String
    let password: String
}

enum RebuildTestUserFactory {
    static func unique(prefix: String = "uit") -> RebuildTestUser {
        let stamp = Int(Date().timeIntervalSince1970)
        return RebuildTestUser(
            username: "\(prefix)_user_\(stamp)",
            email: "\(prefix)_\(stamp)@example.com",
            password: "Pass1234"
        )
    }

    static var seeded: RebuildTestUser {
        RebuildTestUser(username: "testuser", email: "test@example.com", password: "TestPass123!")
    }
}

struct VerificationScreen {
    let app: XCUIApplication

    private var authCodeField: XCUIElement { app.textFields[AccessibilityIdentifiers.Authentication.verificationCodeField] }
    private var onboardingCodeField: XCUIElement { app.textFields[AccessibilityIdentifiers.Onboarding.verificationCodeField] }
    private var authVerifyButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Authentication.verifyButton] }
    private var onboardingVerifyButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Onboarding.verifyButton] }

    var codeField: XCUIElement {
        if authCodeField.exists { return authCodeField }
        return onboardingCodeField
    }

    var verifyButton: XCUIElement {
        if authVerifyButton.exists { return authVerifyButton }
        if onboardingVerifyButton.exists { return onboardingVerifyButton }
        return app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")).firstMatch
    }

    func waitForLoad(timeout: TimeInterval = 15, file: StaticString = #filePath, line: UInt = #line) {
        let loaded = authCodeField.waitForExistence(timeout: timeout)
            || onboardingCodeField.waitForExistence(timeout: timeout)
            || authVerifyButton.waitForExistence(timeout: timeout)
            || onboardingVerifyButton.waitForExistence(timeout: timeout)
        XCTAssertTrue(loaded, "Expected verification screen to load", file: file, line: line)
    }

    func enterCode(_ code: String) {
        codeField.waitForExistenceOrFail(timeout: 10)
        codeField.focusAndType(code, app: app)
    }

    func submitCode() {
        verifyButton.waitForExistenceOrFail(timeout: 10)
        verifyButton.forceTap()
    }

    func tapLogoutIfAvailable() {
        let logout = app.buttons[AccessibilityIdentifiers.Profile.logoutButton].firstMatch
        if logout.waitForExistence(timeout: 3) {
            logout.forceTap()
        }
    }
}

struct MainTabScreenObject {
    let app: XCUIApplication

    var tabBar: XCUIElement { app.tabBars.firstMatch }
    var mainRoot: XCUIElement { app.otherElements[UITestID.Root.mainTabs] }

    var residencesTab: XCUIElement {
        let byID = app.buttons[AccessibilityIdentifiers.Navigation.residencesTab]
        if byID.exists { return byID }
        return app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
    }

    var tasksTab: XCUIElement {
        let byID = app.buttons[AccessibilityIdentifiers.Navigation.tasksTab]
        if byID.exists { return byID }
        return app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
    }

    var contractorsTab: XCUIElement {
        let byID = app.buttons[AccessibilityIdentifiers.Navigation.contractorsTab]
        if byID.exists { return byID }
        return app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
    }

    var documentsTab: XCUIElement {
        let byID = app.buttons[AccessibilityIdentifiers.Navigation.documentsTab]
        if byID.exists { return byID }
        return app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Doc'")).firstMatch
    }

    var profileTab: XCUIElement {
        let byID = app.buttons[AccessibilityIdentifiers.Navigation.profileTab]
        if byID.exists { return byID }
        return app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Profile'")).firstMatch
    }

    func waitForLoad(timeout: TimeInterval = 15) {
        let loaded = mainRoot.waitForExistence(timeout: timeout)
            || tabBar.waitForExistence(timeout: timeout)
        XCTAssertTrue(loaded, "Expected main app root to appear")
    }

    func goToResidences() {
        residencesTab.waitForExistenceOrFail(timeout: 10)
        residencesTab.forceTap()
    }

    func goToTasks() {
        tasksTab.waitForExistenceOrFail(timeout: 10)
        tasksTab.forceTap()
    }

    func goToContractors() {
        contractorsTab.waitForExistenceOrFail(timeout: 10)
        contractorsTab.forceTap()
    }

    func goToDocuments() {
        documentsTab.waitForExistenceOrFail(timeout: 10)
        documentsTab.forceTap()
    }

    func goToProfile() {
        profileTab.waitForExistenceOrFail(timeout: 10)
        profileTab.forceTap()
    }
}

struct ResidenceListScreen {
    let app: XCUIApplication

    var addButton: XCUIElement {
        let byID = app.buttons[AccessibilityIdentifiers.Residence.addButton].firstMatch
        if byID.exists { return byID }
        return app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add'")).firstMatch
    }

    var list: XCUIElement { app.otherElements[AccessibilityIdentifiers.Residence.residencesList] }
    var emptyState: XCUIElement { app.otherElements[AccessibilityIdentifiers.Residence.emptyStateView] }
    var residenceCard: XCUIElement { app.otherElements.matching(identifier: AccessibilityIdentifiers.Residence.residenceCard).firstMatch }

    func waitForLoad(timeout: TimeInterval = 15) {
        let deadline = Date().addingTimeInterval(timeout)
        var loaded = false
        repeat {
            loaded = list.exists
                || emptyState.exists
                || residenceCard.exists
                || addButton.exists
                || app.staticTexts["Residences"].exists
            if loaded { break }
            RunLoop.current.run(until: Date().addingTimeInterval(0.2))
        } while Date() < deadline

        XCTAssertTrue(loaded, "Expected residences list screen to load")
    }

    func openCreateResidence() {
        addButton.waitForExistenceOrFail(timeout: 10)
        addButton.forceTap()
    }
}

struct ResidenceFormScreen {
    let app: XCUIApplication

    var nameField: XCUIElement { app.textFields[AccessibilityIdentifiers.Residence.nameField] }
    var saveButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Residence.saveButton] }
    var cancelButton: XCUIElement { app.buttons[AccessibilityIdentifiers.Residence.formCancelButton] }

    func waitForLoad(timeout: TimeInterval = 15) {
        XCTAssertTrue(nameField.waitForExistence(timeout: timeout), "Expected residence form")
    }

    func enterName(_ value: String) {
        nameField.waitForExistenceOrFail(timeout: 10)
        nameField.focusAndType(value, app: app)
    }

    func save() {
        KeyboardDismisser.dismiss(app: app)
        if !saveButton.exists || !saveButton.isHittable { app.swipeUp() }
        saveButton.waitForExistenceOrFail(timeout: 10)
        saveButton.forceTap()
        _ = saveButton.waitForNonExistence(timeout: 15)
    }

    func cancel() { cancelButton.waitForExistenceOrFail(timeout: 10); cancelButton.forceTap() }
}

enum RebuildSessionAssertions {
    static func assertOnLogin(_ app: XCUIApplication, timeout: TimeInterval = 15, file: StaticString = #filePath, line: UInt = #line) {
        let login = LoginScreenObject(app: app)
        login.waitForLoad(timeout: timeout)
        XCTAssertTrue(app.textFields[UITestID.Auth.usernameField].exists, "Expected login state", file: file, line: line)
    }

    static func assertOnMainApp(_ app: XCUIApplication, timeout: TimeInterval = 15, file: StaticString = #filePath, line: UInt = #line) {
        let main = MainTabScreenObject(app: app)
        main.waitForLoad(timeout: timeout)
        XCTAssertTrue(
            app.otherElements[UITestID.Root.mainTabs].exists || main.tabBar.exists,
            "Expected main app state",
            file: file,
            line: line
        )
    }

    static func assertOnVerification(_ app: XCUIApplication, timeout: TimeInterval = 15, file: StaticString = #filePath, line: UInt = #line) {
        let verify = VerificationScreen(app: app)
        verify.waitForLoad(timeout: timeout, file: file, line: line)
    }
}
