package com.tt.honeyDue

import android.content.Context
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.fixtures.TestUser
import com.tt.honeyDue.models.LoginRequest
import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.network.ApiResult
import com.tt.honeyDue.network.TaskApi
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 kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters

/**
 * Phase 3 — Cleanup tests that run alphabetically last via the `SuiteZZ_`
 * prefix under JUnit's `NAME_ASCENDING` sorter.
 *
 * Ports `iosApp/HoneyDueUITests/SuiteZZ_CleanupTests.swift`, adapted to the
 * Kotlin fixture naming convention. Rather than relying on a seeded admin
 * endpoint (`/admin/settings/clear-all-data` — which the KMM `APILayer` does
 * not wrap) we delete by **prefix** using the authenticated user endpoints
 * that already exist. This mirrors the names produced by `TestResidence`,
 * `TestTask`, and `TestUser.ephemeralUser()`:
 *
 * - Residences whose name begins with `"Test House"` or `"Test Apt"`
 * - Tasks whose title begins with `"Test Task"` or `"Urgent Task"` or `"UITest_"`
 * - Documents whose title begins with `"test_"` or `"UITest_"`
 * - Contractors whose name begins with `"test_"` or `"UITest_"`
 *
 * Each step is idempotent — if there is nothing to clean the test passes
 * trivially. Failures to delete individual items are logged but do not fail
 * the suite; cleanup should never block a subsequent run.
 *
 * Hits the live dev backend configured in `ApiConfig.CURRENT_ENV`.
 */
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class SuiteZZ_CleanupTests {

    private val testUser: TestUser = TestUser.seededTestUser()
    private val taskApi = TaskApi()

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

    @Test
    fun zz01_cleanupTestTasks() = runBlocking {
        val token = ensureLoggedIn() ?: return@runBlocking

        // Force refresh so we see anything parallel suites just created.
        val tasksResult = APILayer.getTasks(forceRefresh = true)
        if (tasksResult !is ApiResult.Success) {
            // Nothing to clean — still considered idempotent success.
            return@runBlocking
        }

        val toDelete = tasksResult.data.columns
            .flatMap { it.tasks }
            .distinctBy { it.id }
            .filter { it.title.matchesTestPrefix(TASK_PREFIXES) }

        toDelete.forEach { task ->
            val res = taskApi.deleteTask(token, task.id)
            if (res !is ApiResult.Success) {
                println("[SuiteZZ] Failed to delete task ${task.id} '${task.title}': $res")
            } else {
                DataManager.removeTask(task.id)
            }
        }
        println("[SuiteZZ] zz01 removed ${toDelete.size} test tasks")
    }

    @Test
    fun zz02_cleanupTestDocuments() = runBlocking {
        ensureLoggedIn() ?: return@runBlocking

        val docsResult = APILayer.getDocuments(forceRefresh = true)
        if (docsResult !is ApiResult.Success) return@runBlocking

        val toDelete = docsResult.data
            .filter { it.title.matchesTestPrefix(DOCUMENT_PREFIXES) }
            .mapNotNull { it.id }

        toDelete.forEach { id ->
            val res = APILayer.deleteDocument(id)
            if (res !is ApiResult.Success) {
                println("[SuiteZZ] Failed to delete document $id: $res")
            }
        }
        println("[SuiteZZ] zz02 removed ${toDelete.size} test documents")
    }

    @Test
    fun zz03_cleanupTestContractors() = runBlocking {
        ensureLoggedIn() ?: return@runBlocking

        val contractorsResult = APILayer.getContractors(forceRefresh = true)
        if (contractorsResult !is ApiResult.Success) return@runBlocking

        val toDelete = contractorsResult.data
            .filter { it.name.matchesTestPrefix(CONTRACTOR_PREFIXES) }
            .map { it.id }

        toDelete.forEach { id ->
            val res = APILayer.deleteContractor(id)
            if (res !is ApiResult.Success) {
                println("[SuiteZZ] Failed to delete contractor $id: $res")
            }
        }
        println("[SuiteZZ] zz03 removed ${toDelete.size} test contractors")
    }

    @Test
    fun zz04_cleanupTestResidences() = runBlocking {
        ensureLoggedIn() ?: return@runBlocking

        val residencesResult = APILayer.getResidences(forceRefresh = true)
        if (residencesResult !is ApiResult.Success) return@runBlocking

        // Skip residences we still need: keep one "Test House" so the next
        // `AAA_SeedTests` run has something to build on if the seed step is
        // skipped. Delete only extras beyond the first Test House match,
        // plus every "Test Apt" residence.
        val allTestResidences = residencesResult.data
            .filter { it.name.matchesTestPrefix(RESIDENCE_PREFIXES) }

        val firstTestHouseId = allTestResidences
            .firstOrNull { it.name.startsWith("Test House") }
            ?.id

        val toDelete = allTestResidences
            .filter { it.id != firstTestHouseId }
            .map { it.id }

        toDelete.forEach { id ->
            val res = APILayer.deleteResidence(id)
            if (res !is ApiResult.Success) {
                println("[SuiteZZ] Failed to delete residence $id: $res")
            }
        }
        println("[SuiteZZ] zz04 removed ${toDelete.size} test residences (kept seed residence id=$firstTestHouseId)")
    }

    @Test
    fun zz05_cleanupTestUsers() = runBlocking {
        // We cannot list-all-users as a normal authenticated user and the
        // KMM APILayer does not wrap any admin delete endpoint. Ephemeral
        // registration users created by Suite1 (`uitest_<n>`) therefore
        // cannot be removed from this client. The Go backend treats them
        // as orphan accounts and expires them out of band.
        //
        // Step left as an idempotent no-op so the numbering matches the
        // iOS suite and the method order stays stable.
        println("[SuiteZZ] zz05 skipped — no client-side user-delete API")
    }

    @Test
    fun zz99_verifyCleanState() = runBlocking {
        ensureLoggedIn() ?: return@runBlocking

        val tasksResult = APILayer.getTasks(forceRefresh = true)
        if (tasksResult is ApiResult.Success) {
            val leftover = tasksResult.data.columns
                .flatMap { it.tasks }
                .filter { it.title.matchesTestPrefix(TASK_PREFIXES) }
            assertTrue(
                "Expected no test-prefixed tasks after cleanup, found: ${leftover.map { it.title }}",
                leftover.isEmpty(),
            )
        }

        val docsResult = APILayer.getDocuments(forceRefresh = true)
        if (docsResult is ApiResult.Success) {
            val leftover = docsResult.data.filter { it.title.matchesTestPrefix(DOCUMENT_PREFIXES) }
            assertTrue(
                "Expected no test-prefixed documents after cleanup, found: ${leftover.map { it.title }}",
                leftover.isEmpty(),
            )
        }

        val contractorsResult = APILayer.getContractors(forceRefresh = true)
        if (contractorsResult is ApiResult.Success) {
            val leftover = contractorsResult.data.filter { it.name.matchesTestPrefix(CONTRACTOR_PREFIXES) }
            assertTrue(
                "Expected no test-prefixed contractors after cleanup, found: ${leftover.map { it.name }}",
                leftover.isEmpty(),
            )
        }
    }

    // ---- Helpers ----

    /**
     * Logs in as the seeded test user so `DataManager.authToken` is
     * populated, then returns the active token. Returns null if login
     * cannot be established — in which case the cleanup step silently
     * no-ops (the backend may already be unreachable).
     */
    private suspend fun ensureLoggedIn(): String? {
        DataManager.authToken.value?.let { return it }

        val loginResult = APILayer.login(
            LoginRequest(username = testUser.username, password = testUser.password),
        )
        return (loginResult as? ApiResult.Success)?.data?.token?.also {
            // login() already writes the token into DataManager; return for
            // direct use by callers that need the raw Bearer value.
        }
    }

    private fun isDataManagerInitialized(): Boolean = 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 (e: Throwable) {
        false
    }

    private fun String.matchesTestPrefix(prefixes: List<String>): Boolean =
        prefixes.any { this.startsWith(it, ignoreCase = false) }

    private companion object {
        // Keep these in sync with the fixtures under
        // `composeApp/src/androidInstrumentedTest/kotlin/com/tt/honeyDue/fixtures/`.
        val TASK_PREFIXES = listOf("Test Task", "Urgent Task", "UITest_", "test_")
        val DOCUMENT_PREFIXES = listOf("test_", "UITest_", "Test Doc")
        val CONTRACTOR_PREFIXES = listOf("test_", "UITest_", "Test Contractor")
        val RESIDENCE_PREFIXES = listOf("Test House", "Test Apt", "UITest_", "test_")
    }
}
