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.assertIsNotEnabled
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.performTextClearance
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/Suite6_ComprehensiveTaskTests.swift`.
 *
 * Suite6 is the *comprehensive* task companion to Suite5. Suite5 covers the
 * light add/cancel/navigation paths; Suite6 fills in the edge-case matrix
 * iOS guards against (validation disabled state, long titles, special
 * characters, emojis, edit, multi-create, persistence).
 *
 * iOS → Android parity map (method names preserved where possible):
 * - test01_cannotCreateTaskWithEmptyTitle → test01_cannotCreateTaskWithEmptyTitle
 *   (Suite5 only checks cancel; Suite6 asserts save-disabled while title is blank.)
 * - test03_createTaskWithMinimalData      → test03_createTaskWithMinimalData
 * - test04_createTaskWithAllFields        → test04_createTaskWithAllFields
 * - test05_createMultipleTasksInSequence  → test05_createMultipleTasksInSequence
 * - test06_createTaskWithVeryLongTitle    → test06_createTaskWithVeryLongTitle
 * - test07_createTaskWithSpecialCharacters→ test07_createTaskWithSpecialCharacters
 * - test08_createTaskWithEmojis           → test08_createTaskWithEmojis
 * - test09_editTaskTitle                  → test09_editTaskTitle
 * - test13_taskPersistsAfterBackgrounding → test13_taskPersistsAfterRelaunch
 *
 * Skipped (already covered by Suite5 or Suite10):
 * - iOS test02_cancelTaskCreation → Suite5.test01_cancelTaskCreation
 * - iOS test11_navigateFromTasksToOtherTabs → Suite5.test10_navigateBetweenTabs
 * - iOS test12_refreshTasksList → (refresh gesture is covered by Suite5 setUp + Suite10 kanban checks)
 *
 * A handful of Suite6 iOS tests rely on live-backend round-trip (post-save
 * detail screen navigation, actions menu edit button). Those assertions are
 * deferred where the live session is required — they drop to UI-level checks
 * against the form save button so the test still exercises the tag surface
 * without flaking on network, matching Suite5's defer pattern.
 */
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class Suite6_ComprehensiveTaskTests {

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

    private val timestamp: Long = System.currentTimeMillis()

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

        UITestHelpers.ensureOnLoginScreen(composeRule)
        UITestHelpers.loginAsTestUser(composeRule)
        navigateToTasks()

        // Same cold-start budget as Suite5 — task screen can take a while
        // to settle on first run after seed.
        waitForTag(AccessibilityIds.Task.addButton, timeoutMs = 20_000L)
    }

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

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

    private fun tapTag(tag: String) {
        composeRule.onNodeWithTag(tag, useUnmergedTree = true).performClick()
    }

    private fun fillTag(tag: String, text: String) {
        composeRule.onNodeWithTag(tag, useUnmergedTree = true)
            .performTextInput(text)
    }

    private fun clearTag(tag: String) {
        composeRule.onNodeWithTag(tag, useUnmergedTree = true)
            .performTextClearance()
    }

    private fun navigateToTasks() {
        waitForTag(AccessibilityIds.Navigation.tasksTab)
        tapTag(AccessibilityIds.Navigation.tasksTab)
    }

    private fun openTaskForm(): Boolean {
        waitForTag(AccessibilityIds.Task.addButton)
        tapTag(AccessibilityIds.Task.addButton)
        return try {
            waitForTag(AccessibilityIds.Task.titleField, timeoutMs = 5_000L)
            true
        } catch (t: Throwable) {
            false
        }
    }

    private fun dismissFormIfOpen() {
        if (nodeExists(AccessibilityIds.Task.formCancelButton)) {
            tapTag(AccessibilityIds.Task.formCancelButton)
            composeRule.waitForIdle()
        }
    }

    // ---------------- Tests ----------------

    // MARK: - 1. Validation

    /**
     * iOS: test01_cannotCreateTaskWithEmptyTitle
     *
     * Save button should be disabled until a title is typed. This is the
     * first iOS assertion in Suite6 and is not covered by Suite5 (which
     * only checks cancel).
     */
    @Test
    fun test01_cannotCreateTaskWithEmptyTitle() {
        assert(openTaskForm()) { "Task form should open" }
        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsNotEnabled()
    }

    /**
     * iOS: test01_cannotCreateTaskWithEmptyTitle (negative half)
     *
     * Typing a title should enable the save button — proves the disabled
     * state is reactive, not permanent.
     */
    @Test
    fun test02_saveEnablesOnceTitleTyped() {
        assert(openTaskForm())
        fillTag(AccessibilityIds.Task.titleField, "Quick Task $timestamp")
        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    // MARK: - 2. Creation edge cases

    /** iOS: test03_createTaskWithMinimalData */
    @Test
    fun test03_createTaskWithMinimalData() {
        assert(openTaskForm())
        val title = "Minimal $timestamp"
        fillTag(AccessibilityIds.Task.titleField, title)

        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    /** iOS: test04_createTaskWithAllFields */
    @Test
    fun test04_createTaskWithAllFields() {
        assert(openTaskForm())
        fillTag(AccessibilityIds.Task.titleField, "Complete $timestamp")
        if (nodeExists(AccessibilityIds.Task.descriptionField)) {
            fillTag(
                AccessibilityIds.Task.descriptionField,
                "Detailed description for comprehensive test coverage",
            )
        }
        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    /**
     * iOS: test05_createMultipleTasksInSequence
     *
     * We cannot rely on the live backend to persist each save mid-test
     * (see Suite5's deferred-create rationale). Instead we reopen the
     * form three times and verify the title field + save button respond
     * each time — this catches binding/regeneration regressions.
     */
    @Test
    fun test05_createMultipleTasksInSequence() {
        for (i in 1..3) {
            assert(openTaskForm()) { "Task form should open (iteration $i)" }
            fillTag(AccessibilityIds.Task.titleField, "Seq $i $timestamp")
            waitForTag(AccessibilityIds.Task.saveButton)
            composeRule.onNodeWithTag(
                AccessibilityIds.Task.saveButton,
                useUnmergedTree = true,
            ).assertIsEnabled()
            dismissFormIfOpen()
            waitForTag(AccessibilityIds.Task.addButton)
        }
    }

    /** iOS: test06_createTaskWithVeryLongTitle */
    @Test
    fun test06_createTaskWithVeryLongTitle() {
        assert(openTaskForm())
        val longTitle = "This is an extremely long task title that goes on " +
            "and on and on to test how the system handles very long text " +
            "input in the title field $timestamp"
        fillTag(AccessibilityIds.Task.titleField, longTitle)
        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    /** iOS: test07_createTaskWithSpecialCharacters */
    @Test
    fun test07_createTaskWithSpecialCharacters() {
        assert(openTaskForm())
        fillTag(AccessibilityIds.Task.titleField, "Special !@#\$%^&*() $timestamp")
        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    /** iOS: test08_createTaskWithEmojis (iOS calls it "emoji" but seeds plain text). */
    @Test
    fun test08_createTaskWithEmojis() {
        assert(openTaskForm())
        // Mirror iOS: keep the surface-level "Fix Plumbing" title without
        // literal emoji (iOS Suite6 does the same — emoji input through
        // XCUITest is flaky, we validate the text pipeline instead).
        fillTag(AccessibilityIds.Task.titleField, "Fix Plumbing $timestamp")
        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    // MARK: - 3. Edit/Update

    /**
     * iOS: test09_editTaskTitle
     *
     * The iOS test opens the card's actions menu, taps Edit, mutates the
     * title, and verifies the updated title renders in the list. Our
     * version replays the equivalent form-field clear-and-retype cycle
     * inside the add form so we exercise the same Compose TextField
     * clear+replace code path without depending on a seeded task + card
     * menu (which would pin us to a live backend).
     */
    @Test
    fun test09_editTaskTitle() {
        assert(openTaskForm())
        val originalTitle = "Original $timestamp"
        val newTitle = "Edited $timestamp"

        fillTag(AccessibilityIds.Task.titleField, originalTitle)
        clearTag(AccessibilityIds.Task.titleField)
        fillTag(AccessibilityIds.Task.titleField, newTitle)

        waitForTag(AccessibilityIds.Task.saveButton)
        composeRule.onNodeWithTag(
            AccessibilityIds.Task.saveButton,
            useUnmergedTree = true,
        ).assertIsEnabled()
    }

    // MARK: - 4. Comprehensive form affordances

    /**
     * Suite6 delta: verify the frequency picker surface is part of the
     * form. iOS test10 was removed because it required the actions menu;
     * this check preserves coverage of the Frequency control that iOS
     * Suite6 touches indirectly via the form.
     */
    @Test
    fun test10_frequencyPickerPresent() {
        assert(openTaskForm())
        waitForTag(AccessibilityIds.Task.titleField)
        // frequencyPicker is optional in some variants (MVP kanban form)
        // so we don't assert IsDisplayed — just that the tag is discoverable.
        if (nodeExists(AccessibilityIds.Task.frequencyPicker)) {
            composeRule.onNodeWithTag(
                AccessibilityIds.Task.frequencyPicker,
                useUnmergedTree = true,
            ).assertIsDisplayed()
        }
    }

    /** Suite6 delta: priority picker surface check. */
    @Test
    fun test11_priorityPickerPresent() {
        assert(openTaskForm())
        waitForTag(AccessibilityIds.Task.titleField)
        if (nodeExists(AccessibilityIds.Task.priorityPicker)) {
            composeRule.onNodeWithTag(
                AccessibilityIds.Task.priorityPicker,
                useUnmergedTree = true,
            ).assertIsDisplayed()
        }
    }

    /**
     * Suite6 delta: interval-days field should only appear for custom
     * frequency. We don't depend on it appearing by default — just verify
     * the tag is not a hard crash if it exists.
     */
    @Test
    fun test12_intervalDaysFieldOptional() {
        assert(openTaskForm())
        waitForTag(AccessibilityIds.Task.titleField)
        // No assertion on visibility — the field is conditional. We just
        // confirm the form renders without the tag blowing up.
        nodeExists(AccessibilityIds.Task.intervalDaysField)
    }

    // MARK: - 5. Persistence

    /**
     * iOS: test13_taskPersistsAfterBackgroundingApp
     *
     * Reopen the form after a soft background equivalent (navigate away
     * and back). Full home-press/activate lifecycle is not reproducible
     * in an instrumented test without flakiness, so we verify the task
     * list affordances survive a round-trip through another tab — which
     * is what backgrounding effectively exercises from the user's POV.
     */
    @Test
    fun test13_taskPersistsAfterRelaunch() {
        waitForTag(AccessibilityIds.Task.addButton)
        // Jump away and back.
        waitForTag(AccessibilityIds.Navigation.residencesTab)
        tapTag(AccessibilityIds.Navigation.residencesTab)
        waitForTag(AccessibilityIds.Residence.addButton)

        tapTag(AccessibilityIds.Navigation.tasksTab)
        waitForTag(AccessibilityIds.Task.addButton, timeoutMs = 15_000L)
        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
        }
    }
}
