package com.tt.honeyDue

import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
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 org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Comprehensive contractor testing suite — 1:1 port of
 * `iosApp/HoneyDueUITests/Suite7_ContractorTests.swift`.
 *
 * Method names mirror the Swift test cases exactly. The helpers at the
 * bottom of the file are localized to this suite rather than added to the
 * shared `ui/screens/` page objects so this port stays self-contained and
 * doesn't conflict with parallel suite ports (Suite1/4/5).
 *
 * Uses the real dev backend via the shared `AAA_SeedTests` login. Tests
 * track their created contractors and rely on `SuiteZZ_Cleanup` (future)
 * plus backend idempotency to avoid poisoning subsequent runs.
 */
@RunWith(AndroidJUnit4::class)
class Suite7_ContractorTests {

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

    private val createdContractorNames: MutableList<String> = mutableListOf()

    @Before
    fun setUp() {
        // AAA_SeedTests.a01_seedTestUserCreated guarantees the backend has
        // `testuser`; we just have to drive the UI to a logged-in state.
        UITestHelpers.ensureOnLoginScreen(rule)
        UITestHelpers.loginAsTestUser(rule)
        navigateToContractors()
        waitForContractorsListReady()
    }

    @After
    fun tearDown() {
        UITestHelpers.tearDown(rule)
        createdContractorNames.clear()
    }

    // MARK: - 1. Validation & Error Handling Tests

    @Test
    fun test01_cannotCreateContractorWithEmptyName() {
        openContractorForm()

        // Fill phone but leave name blank — save should stay disabled.
        fillField(AccessibilityIds.Contractor.phoneField, "555-123-4567")

        val submit = tag(AccessibilityIds.Contractor.saveButton)
        submit.assertExists()
        submit.assertIsNotEnabled()
    }

    @Test
    fun test02_cancelContractorCreation() {
        openContractorForm()

        fillField(AccessibilityIds.Contractor.nameField, "This will be canceled")

        val cancel = tag(AccessibilityIds.Contractor.formCancelButton)
        cancel.assertExists()
        cancel.performClick()

        // Back on contractors tab — add button should be visible again.
        waitForTag(AccessibilityIds.Contractor.addButton)
        assertFalse(
            "Canceled contractor should not exist",
            contractorExists("This will be canceled"),
        )
    }

    // MARK: - 2. Basic Contractor Creation Tests

    @Test
    fun test03_createContractorWithMinimalData() {
        val contractorName = "John Doe ${timestamp()}"
        createContractor(name = contractorName)

        assertTrue(
            "Contractor should appear in list after creation",
            waitForContractor(contractorName),
        )
    }

    @Test
    fun test04_createContractorWithAllFields() {
        val contractorName = "Jane Smith ${timestamp()}"
        createContractor(
            name = contractorName,
            email = "jane.smith@example.com",
            company = "Smith Plumbing Inc",
        )

        assertTrue(
            "Complete contractor should appear in list",
            waitForContractor(contractorName),
        )
    }

    @Test
    fun test05_createContractorWithDifferentSpecialties() {
        val ts = timestamp()
        val specialties = listOf("Plumbing", "Electrical", "HVAC")

        specialties.forEachIndexed { index, _ ->
            val name = "${specialties[index]} Expert ${ts}_$index"
            createContractor(name = name)
            navigateToContractors()
        }

        specialties.forEachIndexed { index, _ ->
            navigateToContractors()
            val name = "${specialties[index]} Expert ${ts}_$index"
            assertTrue(
                "${specialties[index]} contractor should exist in list",
                waitForContractor(name),
            )
        }
    }

    @Test
    fun test06_createMultipleContractorsInSequence() {
        val ts = timestamp()
        for (i in 1..3) {
            val name = "Sequential Contractor $i - $ts"
            createContractor(name = name)
            navigateToContractors()
        }
        for (i in 1..3) {
            val name = "Sequential Contractor $i - $ts"
            assertTrue("Contractor $i should exist in list", waitForContractor(name))
        }
    }

    // MARK: - 3. Edge Case Tests - Phone Numbers

    @Test
    fun test07_createContractorWithDifferentPhoneFormats() {
        val ts = timestamp()
        val phoneFormats = listOf(
            "555-123-4567" to "Dashed",
            "(555) 123-4567" to "Parentheses",
            "5551234567" to "NoFormat",
            "555.123.4567" to "Dotted",
        )
        phoneFormats.forEachIndexed { index, (phone, format) ->
            val name = "$format Phone ${ts}_$index"
            createContractor(name = name, phone = phone)
            navigateToContractors()
        }
        phoneFormats.forEachIndexed { index, (_, format) ->
            navigateToContractors()
            val name = "$format Phone ${ts}_$index"
            assertTrue(
                "Contractor with $format phone should exist",
                waitForContractor(name),
            )
        }
    }

    // MARK: - 4. Edge Case Tests - Emails

    @Test
    fun test08_createContractorWithValidEmails() {
        val ts = timestamp()
        val emails = listOf(
            "simple@example.com",
            "firstname.lastname@example.com",
            "email+tag@example.co.uk",
            "email_with_underscore@example.com",
        )
        emails.forEachIndexed { index, email ->
            val name = "Email Test $index - $ts"
            createContractor(name = name, email = email)
            navigateToContractors()
        }
    }

    // MARK: - 5. Edge Case Tests - Names

    @Test
    fun test09_createContractorWithVeryLongName() {
        val ts = timestamp()
        val longName =
            "John Christopher Alexander Montgomery Wellington III Esquire $ts"
        createContractor(name = longName)
        assertTrue(
            "Long name contractor should exist",
            waitForContractor("John Christopher"),
        )
    }

    @Test
    fun test10_createContractorWithSpecialCharactersInName() {
        val ts = timestamp()
        val specialName = "O'Brien-Smith Jr. $ts"
        createContractor(name = specialName)
        assertTrue(
            "Contractor with special chars should exist",
            waitForContractor("O'Brien"),
        )
    }

    @Test
    fun test11_createContractorWithInternationalCharacters() {
        val ts = timestamp()
        val internationalName = "Jos\u00e9 Garc\u00eda $ts"
        createContractor(name = internationalName)
        assertTrue(
            "Contractor with international chars should exist",
            waitForContractor("Jos\u00e9"),
        )
    }

    @Test
    fun test12_createContractorWithEmojisInName() {
        val ts = timestamp()
        val emojiName = "Bob \uD83D\uDD27 Builder $ts"
        createContractor(name = emojiName)
        assertTrue(
            "Contractor with emojis should exist",
            waitForContractor("Bob"),
        )
    }

    // MARK: - 6. Contractor Editing Tests

    @Test
    fun test13_editContractorName() {
        val ts = timestamp()
        val originalName = "Original Contractor $ts"
        val newName = "Edited Contractor $ts"

        createContractor(name = originalName)
        navigateToContractors()

        assertTrue(
            "Contractor should exist before editing",
            waitForContractor(originalName),
        )
        rule.onNode(hasText(originalName, substring = true), useUnmergedTree = true)
            .performClick()

        // On Android the detail top bar exposes edit directly (no ellipsis
        // intermediate), unlike iOS. Tap the edit button to open the dialog.
        waitForTag(AccessibilityIds.Contractor.editButton)
        tag(AccessibilityIds.Contractor.editButton).performClick()

        waitForTag(AccessibilityIds.Contractor.nameField)
        tag(AccessibilityIds.Contractor.nameField).performTextReplacement(newName)

        val save = tag(AccessibilityIds.Contractor.saveButton)
        if (existsTag(AccessibilityIds.Contractor.saveButton)) {
            save.performClick()
            createdContractorNames.add(newName)
        }
    }

    // test14_updateAllContractorFields — skipped on iOS (multi-field edit
    // unreliable with email keyboard type). Skipped here for parity.

    @Test
    fun test15_navigateFromContractorsToOtherTabs() {
        navigateToContractors()

        // Residences
        waitForTag(AccessibilityIds.Navigation.residencesTab)
        tag(AccessibilityIds.Navigation.residencesTab).performClick()
        waitForTag(AccessibilityIds.Navigation.residencesTab)

        // Back to Contractors
        tag(AccessibilityIds.Navigation.contractorsTab).performClick()
        waitForTag(AccessibilityIds.Navigation.contractorsTab)

        // Tasks
        tag(AccessibilityIds.Navigation.tasksTab).performClick()
        waitForTag(AccessibilityIds.Navigation.tasksTab)

        // Back to Contractors
        tag(AccessibilityIds.Navigation.contractorsTab).performClick()
        waitForTag(AccessibilityIds.Navigation.contractorsTab)
    }

    @Test
    fun test16_refreshContractorsList() {
        navigateToContractors()
        // Refresh button is not explicitly exposed on Android contractors
        // screen; we exercise pull-to-refresh indirectly by re-navigating.
        waitForTag(AccessibilityIds.Navigation.contractorsTab)
        tag(AccessibilityIds.Navigation.contractorsTab).performClick()
        waitForContractorsListReady()
        assertTrue(
            "Add button should remain visible after refresh",
            existsTag(AccessibilityIds.Contractor.addButton),
        )
    }

    @Test
    fun test17_viewContractorDetails() {
        val ts = timestamp()
        val contractorName = "Detail View Test $ts"
        createContractor(
            name = contractorName,
            email = "test@example.com",
            company = "Test Company",
        )
        navigateToContractors()
        assertTrue("Contractor should exist", waitForContractor(contractorName))

        rule.onNode(hasText(contractorName, substring = true), useUnmergedTree = true)
            .performClick()

        // Detail view should load and show at least one contact field.
        waitForTag(AccessibilityIds.Contractor.detailView, timeoutMs = 10_000L)
        tag(AccessibilityIds.Contractor.detailView).assertIsDisplayed()
    }

    // MARK: - 8. Data Persistence Tests

    @Test
    fun test18_contractorPersistsAfterBackgroundingApp() {
        val ts = timestamp()
        val contractorName = "Persistence Test $ts"
        createContractor(name = contractorName)
        navigateToContractors()
        assertTrue(
            "Contractor should exist before backgrounding",
            waitForContractor(contractorName),
        )

        // Backgrounding an Activity from the ComposeTestRule is brittle;
        // exercise the recompose path instead by re-navigating, matching the
        // intent of the iOS test (state survives a scroll/rebind cycle).
        tag(AccessibilityIds.Navigation.tasksTab).performClick()
        waitForTag(AccessibilityIds.Navigation.tasksTab)
        tag(AccessibilityIds.Navigation.contractorsTab).performClick()
        waitForContractorsListReady()

        assertTrue(
            "Contractor should persist after tab cycle",
            waitForContractor(contractorName),
        )
    }

    // ---- Helpers ----

    private fun timestamp(): Long = System.currentTimeMillis() / 1000

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

    private fun existsTag(testTag: String): Boolean = try {
        tag(testTag).assertExists()
        true
    } catch (e: AssertionError) {
        false
    }

    private fun waitForTag(testTag: String, timeoutMs: Long = 10_000L) {
        rule.waitUntil(timeoutMs) { existsTag(testTag) }
    }

    private fun fillField(testTag: String, text: String) {
        waitForTag(testTag)
        tag(testTag).performTextInput(text)
    }

    private fun navigateToContractors() {
        waitForTag(AccessibilityIds.Navigation.contractorsTab)
        tag(AccessibilityIds.Navigation.contractorsTab).performClick()
    }

    private fun waitForContractorsListReady(timeoutMs: Long = 15_000L) {
        rule.waitUntil(timeoutMs) {
            existsTag(AccessibilityIds.Contractor.addButton) ||
                existsTag(AccessibilityIds.Contractor.contractorsList) ||
                existsTag(AccessibilityIds.Contractor.emptyStateView)
        }
    }

    private fun openContractorForm() {
        waitForTag(AccessibilityIds.Contractor.addButton)
        tag(AccessibilityIds.Contractor.addButton).performClick()
        waitForTag(AccessibilityIds.Contractor.nameField)
    }

    private fun contractorExists(name: String): Boolean = try {
        rule.onNode(hasText(name, substring = true), useUnmergedTree = true).assertExists()
        true
    } catch (e: AssertionError) {
        false
    }

    private fun waitForContractor(name: String, timeoutMs: Long = 10_000L): Boolean = try {
        rule.waitUntil(timeoutMs) { contractorExists(name) }
        true
    } catch (e: Throwable) {
        false
    }

    private fun createContractor(
        name: String,
        phone: String? = null,
        email: String? = null,
        company: String? = null,
    ) {
        openContractorForm()

        fillField(AccessibilityIds.Contractor.nameField, name)

        phone?.let { fillField(AccessibilityIds.Contractor.phoneField, it) }
        email?.let { fillField(AccessibilityIds.Contractor.emailField, it) }
        company?.let { fillField(AccessibilityIds.Contractor.companyField, it) }

        waitForTag(AccessibilityIds.Contractor.saveButton)
        tag(AccessibilityIds.Contractor.saveButton).performClick()

        // Dialog dismisses on success — wait for the add button to be
        // interactable again (signals form closed and list refreshed).
        rule.waitUntil(15_000L) {
            !existsTag(AccessibilityIds.Contractor.nameField) &&
                existsTag(AccessibilityIds.Contractor.addButton)
        }
        createdContractorNames.add(name)
    }
}
