package com.correx.core.kernel.orchestration

import com.correx.core.artifacts.kind.AssertionEvaluator
import com.correx.core.artifacts.kind.ContractAssertion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import java.nio.file.Files
import java.nio.file.Path

/**
 * Filesystem/text [ContractAssertionEvaluator] for Gate 2 (design §1). Reads the assertion's target
 * file under the workspace root and returns a verdict with concrete evidence.
 *
 * Handles the `FS` and `TEXT` evaluators outright, and the `AST` ones as text heuristics (they ship
 * heuristic and get promoted to a real parse on demand, design §3.4). `COMPILER` assertions are
 * marked [ContractAssertionVerdict.skipped] — a real `tsc`/`kotlinc` run (the static-analysis command
 * gate) is the authoritative check for those, so re-implementing them here would only add a weaker
 * duplicate.
 */
class FileSystemContractEvaluator : ContractAssertionEvaluator {

    // allowComments/allowTrailingComma make tsconfig.json (JSONC — comments + trailing commas are
    // legal and tsc/Vite parse them) pass valid_json instead of false-failing a valid config file.
    @OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)
    private val json = Json {
        ignoreUnknownKeys = true
        isLenient = true
        allowComments = true
        allowTrailingComma = true
    }

    override suspend fun evaluate(workspaceRoot: Path, assertion: ContractAssertion): ContractAssertionVerdict =
        withContext(Dispatchers.IO) {
            if (assertion.evaluator == AssertionEvaluator.COMPILER) {
                return@withContext skip("covered by static-analysis command gate")
            }
            val path = workspaceRoot.resolve(assertion.target)
            val exists = Files.exists(path) && Files.isRegularFile(path)
            val dirExists = Files.isDirectory(workspaceRoot.resolve(assertion.target))
            when (assertion.id) {
                "file_exists" -> verdict(exists, if (exists) "exists" else "file does not exist")
                "file_nonempty" -> nonEmpty(path, exists)
                "toolchain_installed" -> verdict(dirExists, if (dirExists) "present" else "toolchain dir absent")
                "valid_json" -> validJson(path, exists)
                "json_has_key" -> jsonKey(path, exists, assertion.args["key"].orEmpty())
                "script_defined" -> scriptDefined(path, exists, assertion.args["name"].orEmpty())
                "contains" -> contains(path, exists, assertion.args["pattern"].orEmpty())
                "has_test" -> textMatch(path, exists, "@Test", "no @Test found")
                "migration_present" -> migrationPresent(assertion.target, exists)
                "exports_hook" -> exportsHook(path, exists)
                "exports_default_component" -> textMatch(path, exists, "export default", "no default export")
                "registered_in_serialization" -> skip("cross-file check — deferred to compiler/Option A")
                else -> skip("no evaluator for '${assertion.id}'")
            }
        }

    private fun readOrNull(path: Path, exists: Boolean): String? =
        if (!exists) null else runCatching { Files.readString(path) }.getOrNull()

    private fun nonEmpty(path: Path, exists: Boolean): ContractAssertionVerdict {
        val text = readOrNull(path, exists) ?: return verdict(false, "file does not exist")
        return verdict(text.isNotBlank(), if (text.isNotBlank()) "non-empty" else "file is empty")
    }

    private fun validJson(path: Path, exists: Boolean): ContractAssertionVerdict {
        val text = readOrNull(path, exists) ?: return verdict(false, "file does not exist")
        val ok = runCatching { json.parseToJsonElement(text) }.isSuccess
        return verdict(ok, if (ok) "valid json" else "not parseable as json")
    }

    private fun jsonObj(path: Path, exists: Boolean): JsonObject? =
        readOrNull(path, exists)?.let { runCatching { json.parseToJsonElement(it).jsonObject }.getOrNull() }

    private fun jsonKey(path: Path, exists: Boolean, key: String): ContractAssertionVerdict {
        val obj = jsonObj(path, exists) ?: return verdict(false, "not a json object")
        return verdict(obj.containsKey(key), if (obj.containsKey(key)) "key '$key' present" else "missing key '$key'")
    }

    private fun scriptDefined(path: Path, exists: Boolean, name: String): ContractAssertionVerdict {
        val scripts = jsonObj(path, exists)?.get("scripts")?.let { runCatching { it.jsonObject }.getOrNull() }
            ?: return verdict(false, "no scripts block")
        val hasScript = scripts.containsKey(name)
        return verdict(hasScript, if (hasScript) "script '$name' defined" else "missing script '$name'")
    }

    private fun contains(path: Path, exists: Boolean, pattern: String): ContractAssertionVerdict {
        val text = readOrNull(path, exists) ?: return verdict(false, "file does not exist")
        val hasPattern = text.contains(pattern)
        return verdict(hasPattern, if (hasPattern) "contains '$pattern'" else "missing '$pattern'")
    }

    private fun textMatch(path: Path, exists: Boolean, needle: String, failMsg: String): ContractAssertionVerdict {
        val text = readOrNull(path, exists) ?: return verdict(false, "file does not exist")
        return verdict(text.contains(needle), if (text.contains(needle)) "found '$needle'" else failMsg)
    }

    private fun exportsHook(path: Path, exists: Boolean): ContractAssertionVerdict {
        val text = readOrNull(path, exists) ?: return verdict(false, "file does not exist")
        val ok = HOOK_EXPORT.containsMatchIn(text)
        return verdict(ok, if (ok) "exports a use* function" else "no exported use* hook found")
    }

    private fun migrationPresent(target: String, exists: Boolean): ContractAssertionVerdict {
        val name = target.substringAfterLast('/')
        val ok = exists && name.firstOrNull()?.isDigit() == true
        return verdict(ok, if (ok) "sequential migration file present" else "no sequential-id migration file")
    }

    private fun verdict(passed: Boolean, evidence: String) = ContractAssertionVerdict(passed, evidence)
    private fun skip(reason: String) = ContractAssertionVerdict(passed = true, evidence = reason, skipped = true)

    private companion object {
        val HOOK_EXPORT = Regex("""export\s+(?:default\s+)?(?:async\s+)?(?:function|const)\s+use[A-Z]""")
    }
}
