package com.tt.honeyDue

import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import com.tt.honeyDue.testing.AccessibilityIds
import com.tt.honeyDue.ui.screens.MainTabScreen
import com.tt.honeyDue.ui.screens.Screens

/**
 * Reusable helpers that mirror `iosApp/HoneyDueUITests/UITestHelpers.swift`.
 *
 * Each helper drives off [com.tt.honeyDue.testing.AccessibilityIds] so the
 * same semantic contract holds across iOS and Android. When the production
 * app changes authentication or nav flow, update these helpers rather than
 * every individual test.
 */
object UITestHelpers {
    /** Default credentials for the seeded "testuser" account (matches iOS). */
    const val DEFAULT_TEST_USERNAME = "testuser"
    const val DEFAULT_TEST_PASSWORD = "TestPass123!"

    private fun tagNode(rule: ComposeTestRule, testTag: String): SemanticsNodeInteraction =
        rule.onNodeWithTag(testTag, useUnmergedTree = true)

    /** Non-throwing existence check for a test-tag semantics node. */
    private fun exists(rule: ComposeTestRule, testTag: String): Boolean = try {
        tagNode(rule, testTag).assertExists()
        true
    } catch (e: AssertionError) {
        false
    }

    /** Waits up to [timeoutMs] for a semantics node with [testTag] to exist. */
    private fun waitForTag(
        rule: ComposeTestRule,
        testTag: String,
        timeoutMs: Long = 10_000L,
    ): Boolean = try {
        rule.waitUntil(timeoutMs) { exists(rule, testTag) }
        true
    } catch (e: Throwable) {
        false
    }

    private fun isOnLoginScreen(rule: ComposeTestRule): Boolean =
        exists(rule, AccessibilityIds.Authentication.usernameField)

    private fun isLoggedIn(rule: ComposeTestRule): Boolean =
        exists(rule, AccessibilityIds.Navigation.residencesTab)

    /**
     * Logs out if currently signed in. Noop if already on the login screen.
     */
    fun logout(rule: ComposeTestRule) {
        if (waitForTag(rule, AccessibilityIds.Authentication.usernameField, timeoutMs = 2_000L)) return
        if (!isLoggedIn(rule)) return

        val tabs = MainTabScreen(rule)
        tabs.goToSettings()
        // Some builds back the logout behind an outer profile tab instead;
        // either path converges on the `Profile.LogoutButton` test tag.
        if (waitForTag(rule, AccessibilityIds.Profile.logoutButton, timeoutMs = 5_000L)) {
            tabs.tapLogout()
        }

        // Wait until we transition back to login (15s budget matches iOS).
        waitForTag(rule, AccessibilityIds.Authentication.usernameField, timeoutMs = 15_000L)
    }

    /**
     * Logs in using the provided credentials. Waits for the main tabs to
     * appear before returning. Throws if the tab bar never shows up.
     */
    fun loginAsTestUser(
        rule: ComposeTestRule,
        username: String = DEFAULT_TEST_USERNAME,
        password: String = DEFAULT_TEST_PASSWORD,
    ): MainTabScreen {
        ensureOnLoginScreen(rule)
        val tabs = Screens.login(rule).login(username, password)
        waitForTag(rule, AccessibilityIds.Navigation.residencesTab, timeoutMs = 15_000L)
        return tabs
    }

    /**
     * Best-effort navigation to the login screen from whatever state the app
     * is in. If we are already logged in, logs the user out first.
     */
    fun ensureOnLoginScreen(rule: ComposeTestRule) {
        if (isOnLoginScreen(rule)) return
        if (isLoggedIn(rule)) {
            logout(rule)
            if (isOnLoginScreen(rule)) return
        }
        // Onboarding flow: tap the "login" affordance if present.
        if (exists(rule, AccessibilityIds.Onboarding.loginButton)) {
            tagNode(rule, AccessibilityIds.Onboarding.loginButton).performClick()
        }
        waitForTag(rule, AccessibilityIds.Authentication.usernameField, timeoutMs = 20_000L)
    }

    /**
     * Resets the UI to a known-good starting state. Called from test
     * teardown so residual state from one test doesn't poison the next.
     */
    fun tearDown(rule: ComposeTestRule) {
        try {
            logout(rule)
        } catch (t: Throwable) {
            // Swallow — teardown must never fail a test.
        }
    }
}
