package com.tt.honeyDue

import android.content.Context
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.data.PersistenceManager
import com.tt.honeyDue.storage.TaskCacheManager
import com.tt.honeyDue.storage.TaskCacheStorage
import com.tt.honeyDue.storage.ThemeStorageManager
import com.tt.honeyDue.storage.TokenManager
import com.tt.honeyDue.testing.AccessibilityIds
import org.junit.After
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters

/**
 * Android port of `iosApp/HoneyDueUITests/Suite1_RegistrationTests.swift`.
 *
 * Covers the registration screen's client-side validation, the cancel
 * affordance, and the verification-screen logout path. Tests that require a
 * live backend (full registration → email verification) are deferred and
 * noted in the file header.
 *
 * iOS parity:
 * - test01_registrationScreenElements → test01_registrationScreenElements
 * - test02_cancelRegistration         → test02_cancelRegistration
 * - test03_registrationWithEmptyFields→ test03_registrationWithEmptyFields
 * - test04_registrationWithInvalidEmail→ test04_registrationWithInvalidEmail
 * - test05_mismatchedPasswords        → test05_registrationWithMismatchedPasswords
 * - test06_weakPassword               → test06_registrationWithWeakPassword
 * - test12_logoutFromVerificationScreen→ test12_logoutFromVerificationScreen
 *   (reached via a naive register attempt; the verify screen shows on API
 *   success or we skip gracefully if the backend is unreachable.)
 *
 * Deliberately skipped (require a live backend + email inbox):
 * - test07_successfulRegistrationAndVerification (needs debug verify code `123456`)
 * - test09_registrationWithInvalidVerificationCode
 * - test10_verificationCodeFieldValidation
 * - test11_appRelaunchWithUnverifiedUser (needs app relaunch APIs unavailable
 *   to Compose UI tests)
 */
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class Suite1_RegistrationTests {

    @get:Rule
    val composeRule = createAndroidComposeRule<MainActivity>()

    @Before
    fun setUp() {
        // Mirror MainActivity.onCreate minus UI deps so the shared
        // DataManager / APILayer stack is ready for the UI tests.
        val context = ApplicationProvider.getApplicationContext<Context>()
        if (!isDataManagerInitialized()) {
            DataManager.initialize(
                tokenMgr = TokenManager.getInstance(context),
                themeMgr = ThemeStorageManager.getInstance(context),
                persistenceMgr = PersistenceManager.getInstance(context),
            )
        }
        TaskCacheStorage.initialize(TaskCacheManager.getInstance(context))

        // Start every test from the login screen. If a previous test left us
        // logged in or mid-onboarding, UITestHelpers will recover.
        UITestHelpers.ensureOnLoginScreen(composeRule)
    }

    @After
    fun tearDown() {
        UITestHelpers.tearDown(composeRule)
    }

    // MARK: - Fixtures

    private fun uniqueUsername(): String = "testuser_${System.currentTimeMillis()}"
    private fun uniqueEmail(): String = "test_${System.currentTimeMillis()}@example.com"
    private val testPassword = "Pass1234"

    // MARK: - Helpers

    /** Taps the login screen's Sign Up button and waits for the register form. */
    private fun navigateToRegistration() {
        waitForTag(AccessibilityIds.Authentication.signUpButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.signUpButton,
            useUnmergedTree = true,
        ).performClick()

        // PRECONDITION: Registration form must have appeared.
        waitForTag(AccessibilityIds.Authentication.registerUsernameField)
    }

    /** Fills the four registration form fields. */
    private fun fillRegistrationForm(
        username: String,
        email: String,
        password: String,
        confirmPassword: String,
    ) {
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).performTextInput(username)
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerEmailField,
            useUnmergedTree = true,
        ).performTextInput(email)
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerPasswordField,
            useUnmergedTree = true,
        ).performTextInput(password)
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerConfirmPasswordField,
            useUnmergedTree = true,
        ).performTextInput(confirmPassword)
    }

    /** Best-effort wait until a node with [tag] exists. */
    private fun waitForTag(tag: String, timeoutMs: Long = 10_000L) {
        composeRule.waitUntil(timeoutMs) {
            composeRule.onAllNodesWithTag(tag, useUnmergedTree = true)
                .fetchSemanticsNodes()
                .isNotEmpty()
        }
    }

    private fun nodeExists(tag: String): Boolean =
        composeRule.onAllNodesWithTag(tag, useUnmergedTree = true)
            .fetchSemanticsNodes()
            .isNotEmpty()

    // ---------------- 1. UI / Element Tests ----------------

    /** iOS: test01_registrationScreenElements */
    @Test
    fun test01_registrationScreenElements() {
        navigateToRegistration()

        // STRICT: All form elements must exist.
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerEmailField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerPasswordField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerConfirmPasswordField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerCancelButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // NEGATIVE: Login's Sign Up button should not be reachable while the
        // register screen is on top. (Android uses a navigation destination
        // rather than an iOS sheet, so the login screen is fully gone.)
        assert(!nodeExists(AccessibilityIds.Authentication.signUpButton)) {
            "Login Sign Up button should not be present on registration screen"
        }
    }

    /** iOS: test02_cancelRegistration */
    @Test
    fun test02_cancelRegistration() {
        navigateToRegistration()

        // PRECONDITION: On registration screen.
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // Cancel → back to login.
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerCancelButton,
            useUnmergedTree = true,
        ).performClick()

        waitForTag(AccessibilityIds.Authentication.usernameField)
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.usernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // Register fields must be gone.
        assert(!nodeExists(AccessibilityIds.Authentication.registerUsernameField)) {
            "Registration form must disappear after cancel"
        }
    }

    // ---------------- 2. Client-Side Validation Tests ----------------

    /** iOS: test03_registrationWithEmptyFields */
    @Test
    fun test03_registrationWithEmptyFields() {
        navigateToRegistration()

        // With empty fields the Register button is disabled in the Kotlin
        // implementation. Instead of tapping (noop), assert the button isn't
        // enabled — this is the same user-visible guarantee as iOS (which
        // requires the field-required error when tapping with empty fields).
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // NEGATIVE: No navigation to verify happened.
        assert(!nodeExists(AccessibilityIds.Authentication.verificationCodeField)) {
            "Should NOT navigate to verification with empty fields"
        }
        // STRICT: Still on registration form.
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test04_registrationWithInvalidEmail */
    @Test
    fun test04_registrationWithInvalidEmail() {
        navigateToRegistration()

        fillRegistrationForm(
            username = "testuser",
            email = "invalid-email",
            password = testPassword,
            confirmPassword = testPassword,
        )

        // Even with an invalid email the client-side button is enabled; tapping
        // it will relay the error. We assert we stay on registration (i.e. no
        // verify screen appears).
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerButton,
            useUnmergedTree = true,
        ).performClick()

        // Give the UI a beat to react, but we stay on registration regardless.
        composeRule.waitForIdle()

        assert(!nodeExists(AccessibilityIds.Authentication.verificationCodeField)) {
            "Should NOT navigate to verification with invalid email"
        }
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test05_registrationWithMismatchedPasswords */
    @Test
    fun test05_registrationWithMismatchedPasswords() {
        navigateToRegistration()

        fillRegistrationForm(
            username = "testuser",
            email = "test@example.com",
            password = "Password123!",
            confirmPassword = "DifferentPassword123!",
        )

        // Button is disabled when passwords don't match → we stay on registration.
        assert(!nodeExists(AccessibilityIds.Authentication.verificationCodeField)) {
            "Should NOT navigate to verification with mismatched passwords"
        }
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test06_registrationWithWeakPassword */
    @Test
    fun test06_registrationWithWeakPassword() {
        navigateToRegistration()

        fillRegistrationForm(
            username = "testuser",
            email = "test@example.com",
            password = "weak",
            confirmPassword = "weak",
        )

        // Button should be disabled because the password requirements aren't met;
        // there is no way the verify screen can appear.
        assert(!nodeExists(AccessibilityIds.Authentication.verificationCodeField)) {
            "Should NOT navigate to verification with weak password"
        }
        composeRule.onNodeWithTag(
            AccessibilityIds.Authentication.registerUsernameField,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    // ---------------- DataManager init helper ----------------

    /**
     * Read the private `_isInitialized` StateFlow value via reflection.
     * Mirrors the same trick used in `AAA_SeedTests` — lets us skip
     * reinitializing `DataManager` if the instrumentation process has already
     * bootstrapped it.
     */
    private fun isDataManagerInitialized(): Boolean {
        return try {
            val field = DataManager::class.java.getDeclaredField("_isInitialized")
            field.isAccessible = true
            @Suppress("UNCHECKED_CAST")
            val flow = field.get(DataManager) as kotlinx.coroutines.flow.MutableStateFlow<Boolean>
            flow.value
        } catch (t: Throwable) {
            false
        }
    }
}
