package com.tt.honeyDue

import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performTextReplacement
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.tt.honeyDue.testing.AccessibilityIds
import com.tt.honeyDue.ui.screens.MainTabScreen
import com.tt.honeyDue.ui.screens.ResidencesFormPageObject
import com.tt.honeyDue.ui.screens.ResidencesListPageObject
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
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

/**
 * Suite4 — Comprehensive residence tests.
 *
 * Ports `iosApp/HoneyDueUITests/Suite4_ComprehensiveResidenceTests.swift`
 * 1:1 with matching method names. Each Kotlin method keeps the numeric
 * prefix of its iOS counterpart so `@FixMethodOrder(NAME_ASCENDING)`
 * preserves the same execution order.
 *
 * These tests exercise the real dev backend via the instrumentation process
 * (mirroring iOS behavior) — no mocks. The suite depends on seeded accounts
 * from `AAA_SeedTests` so `testuser` exists with at least one residence.
 *
 * **Test ownership**: residence screens only. Other surfaces are covered by
 * sibling suites (Suite1 auth, Suite5 tasks, Suite7 contractors).
 */
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class Suite4_ComprehensiveResidenceTests {

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

    // Tracks residence names created by UI tests so they can be scrubbed via API in teardown.
    private val createdResidenceNames: MutableList<String> = mutableListOf()

    @Before
    fun setUp() {
        // Dismiss any lingering form from a previous test (defensive — parallel or
        // retry runs occasionally leave a residence form open).
        val form = ResidencesFormPageObject(composeRule)
        if (form.isDisplayed()) form.tapCancel()

        // Ensure we're authenticated and on the residences tab.
        UITestHelpers.loginAsTestUser(composeRule)
        MainTabScreen(composeRule).tapResidencesTab()

        val list = ResidencesListPageObject(composeRule)
        list.waitForLoad()
    }

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

    // region Helpers

    private fun list() = ResidencesListPageObject(composeRule)

    private fun navigateToResidences() {
        MainTabScreen(composeRule).tapResidencesTab()
        list().waitForLoad()
    }

    private fun createResidence(
        name: String,
        street: String = "123 Test St",
        city: String = "TestCity",
        stateProvince: String = "TS",
        postal: String = "12345",
    ) {
        val form = list().tapAddResidence()
        form.enterName(name)
        form.fillAddress(street, city, stateProvince, postal)
        form.tapSave()
        form.waitForDismiss()
        createdResidenceNames.add(name)
    }

    private fun findResidenceNodeExists(nameSubstring: String, timeoutMs: Long = 15_000L): Boolean = try {
        composeRule.waitUntil(timeoutMs) {
            try {
                composeRule.onNode(hasText(nameSubstring, substring = true), useUnmergedTree = true)
                    .assertExists()
                true
            } catch (e: AssertionError) {
                false
            }
        }
        true
    } catch (t: Throwable) {
        false
    }

    // endregion

    // MARK: - 1. Error/Validation Tests

    @Test
    fun test01_cannotCreateResidenceWithEmptyName() {
        val form = list().tapAddResidence()

        // Leave name blank, fill only address.
        form.fillAddress(street = "123 Test St", city = "TestCity", stateProvince = "TS", postal = "12345")

        // Save button must be disabled while name is empty.
        form.assertSaveDisabled()

        // Clean up so the next test starts on the list.
        form.tapCancel()
    }

    @Test
    fun test02_cancelResidenceCreation() {
        val form = list().tapAddResidence()
        form.enterName("This will be canceled")

        form.tapCancel()

        // Back on residences tab — tab bar tag should exist.
        assertTrue(
            "Should be back on residences list",
            composeRule.onNodeWithTagExists(AccessibilityIds.Navigation.residencesTab),
        )

        // Canceled residence must not appear in the list.
        assertFalse(
            "Canceled residence should not exist in list",
            findResidenceNodeExists("This will be canceled", timeoutMs = 3_000L),
        )
    }

    // MARK: - 2. Creation Tests

    @Test
    fun test03_createResidenceWithMinimalData() {
        val name = uniqueName("Minimal Home")
        createResidence(name = name)

        navigateToResidences()
        assertTrue("Residence should appear in list", findResidenceNodeExists(name))
    }

    // test04 skipped on iOS too — no seeded residence types.

    @Test
    fun test05_createMultipleResidencesInSequence() {
        val ts = System.currentTimeMillis()
        for (i in 1..3) {
            val name = "Sequential Home $i - $ts"
            createResidence(name = name)
            navigateToResidences()
        }
        for (i in 1..3) {
            val name = "Sequential Home $i - $ts"
            assertTrue("Residence $i should exist in list", findResidenceNodeExists(name))
        }
    }

    @Test
    fun test06_createResidenceWithVeryLongName() {
        val longName = uniqueName(
            "This is an extremely long residence name that goes on and on and on to test how the system handles very long text input in the name field",
        )
        createResidence(name = longName)

        navigateToResidences()
        assertTrue(
            "Long name residence should exist",
            findResidenceNodeExists("extremely long residence"),
        )
    }

    @Test
    fun test07_createResidenceWithSpecialCharacters() {
        val name = uniqueName("Special !@#\$%^&*() Home")
        createResidence(name = name)

        navigateToResidences()
        assertTrue(
            "Residence with special chars should exist",
            findResidenceNodeExists("Special"),
        )
    }

    @Test
    fun test08_createResidenceWithEmojis() {
        // Matches iOS text ("Beach House") — no emoji literal in payload to avoid
        // flaky text matching when some platforms render emoji variants.
        val name = uniqueName("Beach House")
        createResidence(name = name)

        navigateToResidences()
        assertTrue(
            "Residence with 'Beach House' label should exist",
            findResidenceNodeExists("Beach House"),
        )
    }

    @Test
    fun test09_createResidenceWithInternationalCharacters() {
        val name = uniqueName("Chateau Montreal")
        createResidence(name = name)

        navigateToResidences()
        assertTrue(
            "Residence with international chars should exist",
            findResidenceNodeExists("Chateau"),
        )
    }

    @Test
    fun test10_createResidenceWithVeryLongAddress() {
        val name = uniqueName("Long Address Home")
        createResidence(
            name = name,
            street = "123456789 Very Long Street Name That Goes On And On Boulevard Apartment Complex Unit 42B",
            city = "VeryLongCityNameThatTestsTheLimit",
            stateProvince = "CA",
            postal = "12345-6789",
        )

        navigateToResidences()
        assertTrue(
            "Residence with long address should exist",
            findResidenceNodeExists(name),
        )
    }

    // MARK: - 3. Edit/Update Tests

    @Test
    fun test11_editResidenceName() {
        val originalName = uniqueName("Original Name")
        val newName = uniqueName("Edited Name")

        createResidence(name = originalName)
        navigateToResidences()

        val detail = list().openResidence(originalName)
        val form = detail.tapEdit()
        form.replaceName(newName)
        form.tapSave()
        form.waitForDismiss()

        createdResidenceNames.add(newName)

        navigateToResidences()
        assertTrue(
            "Residence should show updated name",
            findResidenceNodeExists(newName),
        )
    }

    @Test
    fun test12_updateAllResidenceFields() {
        val originalName = uniqueName("Update All Fields")
        val newName = uniqueName("All Fields Updated")

        createResidence(
            name = originalName,
            street = "123 Old St",
            city = "OldCity",
            stateProvince = "OC",
            postal = "11111",
        )
        navigateToResidences()

        val detail = list().openResidence(originalName)
        val form = detail.tapEdit()

        form.replaceName(newName)
        // Replace address fields directly via the compose rule. FormTextField has
        // no clear helper — performTextReplacement handles it without dismissKeyboard
        // gymnastics.
        composeRule.onNodeWithTag(AccessibilityIds.Residence.streetAddressField, useUnmergedTree = true)
            .performTextReplacement("999 Updated Avenue")
        composeRule.onNodeWithTag(AccessibilityIds.Residence.cityField, useUnmergedTree = true)
            .performTextReplacement("NewCity")
        composeRule.onNodeWithTag(AccessibilityIds.Residence.stateProvinceField, useUnmergedTree = true)
            .performTextReplacement("NC")
        composeRule.onNodeWithTag(AccessibilityIds.Residence.postalCodeField, useUnmergedTree = true)
            .performTextReplacement("99999")

        form.tapSave()
        form.waitForDismiss()

        createdResidenceNames.add(newName)

        navigateToResidences()
        assertTrue(
            "Residence should show updated name in list",
            findResidenceNodeExists(newName),
        )
    }

    // MARK: - 4. View/Navigation Tests

    @Test
    fun test13_viewResidenceDetails() {
        val name = uniqueName("Detail View Test")
        createResidence(name = name)

        navigateToResidences()
        val detail = list().openResidence(name)
        detail.waitForLoad()

        // Detail view is marked with AccessibilityIds.Residence.detailView on its Scaffold.
        assertTrue(
            "Detail view should display with edit button or detail tag",
            composeRule.onNodeWithTagExists(AccessibilityIds.Residence.editButton) ||
                composeRule.onNodeWithTagExists(AccessibilityIds.Residence.detailView),
        )
    }

    @Test
    fun test14_navigateFromResidencesToOtherTabs() {
        val tabs = MainTabScreen(composeRule)

        tabs.tapResidencesTab()
        tabs.tapTasksTab()
        assertTrue(
            "Tasks tab should be visible after selection",
            composeRule.onNodeWithTagExists(AccessibilityIds.Navigation.tasksTab),
        )

        tabs.tapResidencesTab()
        assertTrue(
            "Residences tab should reselect",
            composeRule.onNodeWithTagExists(AccessibilityIds.Navigation.residencesTab),
        )

        tabs.tapContractorsTab()
        assertTrue(
            "Contractors tab should be visible",
            composeRule.onNodeWithTagExists(AccessibilityIds.Navigation.contractorsTab),
        )

        tabs.tapResidencesTab()
        assertTrue(
            "Residences tab should reselect after contractors",
            composeRule.onNodeWithTagExists(AccessibilityIds.Navigation.residencesTab),
        )
    }

    @Test
    fun test15_refreshResidencesList() {
        // Android relies on PullToRefreshBox; the UI test harness cannot reliably
        // gesture a pull-to-refresh, so the test verifies we're still on the
        // residences tab after re-selecting it (mirrors iOS fallback path).
        navigateToResidences()
        MainTabScreen(composeRule).tapResidencesTab()

        assertTrue(
            "Should still be on Residences tab after refresh",
            composeRule.onNodeWithTagExists(AccessibilityIds.Navigation.residencesTab),
        )
    }

    // MARK: - 5. Persistence Tests

    @Test
    fun test16_residencePersistsAfterBackgroundingApp() {
        val name = uniqueName("Persistence Test")
        createResidence(name = name)

        navigateToResidences()
        assertTrue("Residence should exist before backgrounding", findResidenceNodeExists(name))

        // Android equivalent of "background and reactivate": waitForIdle is all the
        // Compose test harness supports cleanly. The real backgrounding path is
        // covered by MainActivity lifecycle tests elsewhere.
        composeRule.waitForIdle()

        navigateToResidences()
        assertTrue("Residence should persist after backgrounding", findResidenceNodeExists(name))
    }

    // region Private

    private fun uniqueName(base: String): String = "$base ${System.currentTimeMillis()}"

    // endregion
}

/**
 * Non-throwing probe for a semantics node with the given test tag. The Compose
 * Test matcher throws an AssertionError when missing; JUnit would treat that
 * as a hard failure, so tests use this helper for probe-style checks instead.
 */
private fun ComposeTestRule.onNodeWithTagExists(testTag: String): Boolean = try {
    onNodeWithTag(testTag, useUnmergedTree = true).assertExists()
    true
} catch (e: AssertionError) {
    false
}
