package com.tt.honeyDue.architecture

import java.io.File
import kotlin.test.Test
import kotlin.test.fail

/**
 * Architecture regression gate.
 *
 * Scans `composeApp/src/commonMain/kotlin/com/tt/honeyDue/viewmodel/` and
 * asserts every ViewModel either:
 *   a) accepts `dataManager: IDataManager` as a constructor parameter, or
 *   b) is explicitly allowlisted in [WORKFLOW_ONLY_VMS] as a
 *      workflow/mutation-only VM.
 *
 * Prevents the Dec 3 2025 regression (4 VMs holding independent
 * `MutableStateFlow` read-state instead of deriving from DataManager).
 * See `docs/parity-gallery.md` "Known limitations" for the history.
 *
 * Workflow / write-only (no read-state to mirror):
 *   * `TaskCompletionViewModel`  — single-shot create mutation
 *   * `OnboardingViewModel`       — wizard form + per-step ApiResult
 *   * `PasswordResetViewModel`    — wizard form + per-step ApiResult
 *
 * Everyone else must accept the `dataManager` ctor param.
 */
class NoIndependentViewModelStateFileScanTest {

    @Test
    fun every_read_state_vm_accepts_iDataManager_ctor_param() {
        val vmSources = findViewModelSources()
        val violations = mutableListOf<String>()

        vmSources.forEach { file ->
            val name = file.name
            if (name in WORKFLOW_ONLY_VMS) return@forEach

            val body = file.readText()
            val hasCtorParam = body.contains(Regex("""dataManager:\s*IDataManager"""))
            if (!hasCtorParam) {
                violations.add(
                    "$name — expected `dataManager: IDataManager = DataManager` " +
                        "constructor parameter. Without this, read-state can't derive " +
                        "from the DataManager single source of truth and snapshot " +
                        "tests can't substitute a fixture. " +
                        "If this VM genuinely has no read-state (workflow / mutation only), " +
                        "add its filename to WORKFLOW_ONLY_VMS with justification.",
                )
            }
        }

        if (violations.isNotEmpty()) {
            fail(
                "ViewModel architecture regression (see docs/parity-gallery.md):\n" +
                    violations.joinToString(separator = "\n") { "  - $it" },
            )
        }
    }

    @Test
    fun read_state_flows_should_be_derived_not_independent() {
        val vmSources = findViewModelSources()
        val violations = mutableListOf<String>()

        // Names of fields that track one-shot mutation/workflow feedback —
        // exempt from the "must be derived" rule.
        val mutationFieldPrefixes = listOf(
            "create", "update", "delete", "toggle", "download",
            "upload", "archive", "unarchive", "cancel", "uncancel",
            "mark", "generate", "request", "submit", "login", "register",
            "reset", "forgot", "verify", "apple", "google",
            "join", "addNew", "addTask", "taskAddNew",
            "action", "currentStep", "resetToken", "email", "selected",
            // Local-only state not cached by DataManager:
            "category", // NotificationPreferencesViewModel per-channel local toggles
        )

        vmSources.forEach { file ->
            val name = file.name
            if (name in WORKFLOW_ONLY_VMS) return@forEach
            if (name == "AuthViewModel.kt") return@forEach  // 11 one-shot states, all mutation-feedback; allowlisted as a file

            val body = file.readText()
            val mutableReads = Regex("""private val (_[a-zA-Z]+State)\s*=\s*MutableStateFlow""")
                .findAll(body).map { it.groupValues[1] }.toList()

            mutableReads.forEach { fieldName ->
                val root = fieldName.removePrefix("_").removeSuffix("State")
                val isMutationFeedback = mutationFieldPrefixes.any {
                    root.lowercase().startsWith(it.lowercase())
                }
                if (!isMutationFeedback) {
                    violations.add(
                        "$name — field `$fieldName` looks like cached read-state " +
                            "(not matching any mutation-feedback prefix). Derive it from " +
                            "DataManager via `dataManager.xxx.map { ... }.stateIn(...)` " +
                            "instead of owning a MutableStateFlow. If this field really " +
                            "is mutation feedback, add its name prefix to " +
                            "mutationFieldPrefixes in this test.",
                    )
                }
            }
        }

        if (violations.isNotEmpty()) {
            fail(
                "ViewModel state-ownership regression (see docs/parity-gallery.md):\n" +
                    violations.joinToString(separator = "\n") { "  - $it" },
            )
        }
    }

    private fun findViewModelSources(): List<File> {
        // Test cwd is `composeApp/` — resolve from the project dir.
        val vmDir = File("src/commonMain/kotlin/com/tt/honeyDue/viewmodel")
        check(vmDir.exists()) {
            "expected VM source directory not found: ${vmDir.absolutePath} (cwd=${File(".").absolutePath})"
        }
        return vmDir.listFiles { f -> f.extension == "kt" }?.toList().orEmpty()
    }

    companion object {
        /** VMs that legitimately don't need DataManager injection. */
        val WORKFLOW_ONLY_VMS: Set<String> = setOf(
            "TaskCompletionViewModel.kt",
            "OnboardingViewModel.kt",
            "PasswordResetViewModel.kt",
        )
    }
}
