package com.tt.honeyDue

import android.content.Context
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
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.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/Suite5_TaskTests.swift`.
 *
 * Covers the task list's add-button affordance, new-task form open/cancel,
 * and cross-tab navigation between Tasks → Contractors → Documents →
 * Residences. The live-backend create flow (iOS test06/07 which poll the API
 * after save) is deferred here because instrumented tests cannot rely on the
 * dev backend being reachable; the UI-side equivalent (open form → see title
 * field → cancel) is covered by test01/05.
 *
 * iOS parity:
 * - test01_cancelTaskCreation        → test01_cancelTaskCreation
 * - test02_tasksTabExists            → test02_tasksTabExists
 * - test03_viewTasksList             → test03_viewTasksList
 * - test04_addTaskButtonEnabled      → test04_addTaskButtonEnabled
 * - test05_navigateToAddTask         → test05_navigateToAddTask
 * - test08_navigateToContractors     → test08_navigateToContractors
 * - test09_navigateToDocuments       → test09_navigateToDocuments
 * - test10_navigateBetweenTabs       → test10_navigateBetweenTabs
 *
 * Deliberately deferred (require a live authenticated session + dev backend
 * reachability, which the parallel Residence/Contractor suites avoid the
 * same way):
 * - test06_createBasicTask   — verifies created task via API polling
 * - test07_viewTaskDetails   — creates a task then inspects action menu
 */
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class Suite5_TaskTests {

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

    @Before
    fun setUp() {
        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))

        // Precondition: the task add button only enables when a residence
        // exists. Log in as the seeded test user (AAA_SeedTests guarantees
        // the testuser + residence + task exist) and navigate to the Tasks
        // tab so each test starts in the same known state.
        UITestHelpers.ensureOnLoginScreen(composeRule)
        UITestHelpers.loginAsTestUser(composeRule)
        navigateToTasks()

        // Wait for task screen to settle — add button must be reachable.
        waitForTag(AccessibilityIds.Task.addButton, timeoutMs = 20_000L)
    }

    @After
    fun tearDown() {
        // Dismiss the task dialog if it was left open by a failing assertion.
        if (nodeExists(AccessibilityIds.Task.formCancelButton)) {
            composeRule.onNodeWithTag(
                AccessibilityIds.Task.formCancelButton,
                useUnmergedTree = true,
            ).performClick()
            composeRule.waitForIdle()
        }
        UITestHelpers.tearDown(composeRule)
    }

    // MARK: - Helpers

    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()

    /** Taps the Tasks tab and waits for the task screen affordances. */
    private fun navigateToTasks() {
        waitForTag(AccessibilityIds.Navigation.tasksTab)
        composeRule.onNodeWithTag(
            AccessibilityIds.Navigation.tasksTab,
            useUnmergedTree = true,
        ).performClick()
    }

    private fun navigateToContractors() {
        waitForTag(AccessibilityIds.Navigation.contractorsTab)
        composeRule.onNodeWithTag(
            AccessibilityIds.Navigation.contractorsTab,
            useUnmergedTree = true,
        ).performClick()
    }

    private fun navigateToDocuments() {
        waitForTag(AccessibilityIds.Navigation.documentsTab)
        composeRule.onNodeWithTag(
            AccessibilityIds.Navigation.documentsTab,
            useUnmergedTree = true,
        ).performClick()
    }

    private fun navigateToResidences() {
        waitForTag(AccessibilityIds.Navigation.residencesTab)
        composeRule.onNodeWithTag(
            AccessibilityIds.Navigation.residencesTab,
            useUnmergedTree = true,
        ).performClick()
    }

    // MARK: - 1. Validation

    /** iOS: test01_cancelTaskCreation */
    @Test
    fun test01_cancelTaskCreation() {
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).performClick()

        // PRECONDITION: task form opened (title field visible).
        waitForTag(AccessibilityIds.Task.titleField)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.titleField,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // Cancel the form.
        waitForTag(AccessibilityIds.Task.formCancelButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.formCancelButton,
            useUnmergedTree = true,
        ).performClick()

        // Verify we're back on the task list (add button reachable again,
        // title field gone).
        waitForTag(AccessibilityIds.Task.addButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        assert(!nodeExists(AccessibilityIds.Task.titleField)) {
            "Task form title field should disappear after cancel"
        }
    }

    // MARK: - 2. View/List

    /** iOS: test02_tasksTabExists */
    @Test
    fun test02_tasksTabExists() {
        // Tab bar exists (Tasks tab is how we got here).
        composeRule.onNodeWithTag(
            AccessibilityIds.Navigation.tasksTab,
            useUnmergedTree = true,
        ).assertIsDisplayed()
        // Task add button proves we're on the Tasks tab.
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test03_viewTasksList */
    @Test
    fun test03_viewTasksList() {
        // Verified by the add button existence from setUp.
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test04_addTaskButtonEnabled */
    @Test
    fun test04_addTaskButtonEnabled() {
        // Add button is enabled because the seed residence exists.
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    /** iOS: test05_navigateToAddTask */
    @Test
    fun test05_navigateToAddTask() {
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).performClick()

        waitForTag(AccessibilityIds.Task.titleField)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.titleField,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // Save button should be present inside the form.
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        // Cleanup: dismiss the form.
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.formCancelButton,
            useUnmergedTree = true,
        ).performClick()
    }

    // MARK: - 5. Cross-tab Navigation

    /** iOS: test08_navigateToContractors */
    @Test
    fun test08_navigateToContractors() {
        navigateToContractors()
        waitForTag(AccessibilityIds.Contractor.addButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Contractor.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test09_navigateToDocuments */
    @Test
    fun test09_navigateToDocuments() {
        navigateToDocuments()
        waitForTag(AccessibilityIds.Document.addButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Document.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

    /** iOS: test10_navigateBetweenTabs */
    @Test
    fun test10_navigateBetweenTabs() {
        navigateToResidences()
        waitForTag(AccessibilityIds.Residence.addButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Residence.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()

        navigateToTasks()
        waitForTag(AccessibilityIds.Task.addButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.addButton,
            useUnmergedTree = true,
        ).assertIsDisplayed()
    }

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

    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
        }
    }
}
