package com.correx.core.config

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

class ConfigLoaderTest {
    @Test
    fun `load returns defaults when config file missing`() {
        val config = CorrexConfig()
        assertEquals("localhost", config.server.host)
        assertEquals(8080, config.server.port)
        assertEquals("dark", config.tui.theme)
        assertEquals(5, config.tui.sessionListLimit)
        assertEquals("human", config.cli.defaultOutput)
        assertEquals(emptyList<String>(), config.tools.allowedWorkspaceRoots)
    }

    @Test
    fun `parseToml parses valid toml content`() {
        val toml = """
            [server]
            host = "0.0.0.0"
            port = 9000

            [tui]
            theme = "light"
            session_list_limit = 10

            [cli]
            default_output = "json"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals("0.0.0.0", result.server.host)
        assertEquals(9000, result.server.port)
        assertEquals("light", result.tui.theme)
        assertEquals(10, result.tui.sessionListLimit)
        assertEquals("json", result.cli.defaultOutput)
    }

    @Test
    fun `parseToml parses artifacts array-of-tables and separates them from models and sections`() {
        val toml = """
            [[models]]
            id = "m1"
            model_path = "/m1.gguf"

            [[artifacts]]
            id = "review_report"
            schema_path = "schemas/review.json"
            llm_emitted = true

            [[artifacts]]
            id = "plan"
            schema_path = "schemas/plan.json"

            [server]
            port = 1234
        """.trimIndent()

        val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(1, result.models.size)
        assertEquals(1234, result.server.port)
        assertEquals(2, result.artifacts.size)
        assertEquals("review_report", result.artifacts[0].id)
        assertEquals("schemas/review.json", result.artifacts[0].schemaPath)
        assertEquals(true, result.artifacts[0].llmEmitted)
        assertEquals("plan", result.artifacts[1].id)
        assertEquals(false, result.artifacts[1].llmEmitted)
    }

    @Test
    fun `parseToml skips comments and empty lines`() {
        val toml = """
            # This is a comment
            [server]
            # Another comment
            host = "localhost"

            # Empty line above
            port = 8080
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals("localhost", result.server.host)
        assertEquals(8080, result.server.port)
    }

    @Test
    fun `configPath returns a valid path`() {
        val configPath = ConfigLoader.configPath()
        // Verify it returns a non-null Path object
        assertEquals("config.toml", configPath.fileName.toString())
    }

    @Test
    fun `parseToml parses providers with inline table capabilities`() {
        val toml = """
            [server]
            host = "localhost"

            [[providers]]
            id = "local-llama"
            type = "llamacpp"
            model_id = "mistral-7b"
            model_path = "/path/to/model.gguf"
            url = "http://127.0.0.1:10000"
            capabilities = { General = 1.0, Coding = 0.7, Reasoning = 0.6 }

            [[providers]]
            id = "alt-llama"
            type = "llamacpp"
            model_id = "neural-chat-7b"
            model_path = "/path/to/model2.gguf"
            url = "http://127.0.0.1:10001"
            capabilities = { General = 0.9, Coding = 0.8 }
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(2, result.providers.size)
        assertEquals("local-llama", result.providers[0].id)
        assertEquals("llamacpp", result.providers[0].type)
        assertEquals("mistral-7b", result.providers[0].modelId)
        assertEquals("/path/to/model.gguf", result.providers[0].modelPath)
        assertEquals("http://127.0.0.1:10000", result.providers[0].url)
        assertEquals(3, result.providers[0].capabilities.size)
        assertEquals(1.0, result.providers[0].capabilities["General"])
        assertEquals(0.7, result.providers[0].capabilities["Coding"])
        assertEquals(0.6, result.providers[0].capabilities["Reasoning"])

        assertEquals("alt-llama", result.providers[1].id)
        assertEquals("neural-chat-7b", result.providers[1].modelId)
        assertEquals(2, result.providers[1].capabilities.size)
    }

    @Test
    fun `parseToml parses nested tool sections with enable flags`() {
        val toml = """
            [tools]
            sandbox_root = "/tmp/correx"

            [tools.shell]
            enabled = false
            allowed_executables = ["bash", "sh", "python3"]

            [tools.file_read]
            enabled = true

            [tools.file_write]
            enabled = false

            [tools.file_edit]
            enabled = true
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(false, result.tools.shellEnabled)
        assertEquals(true, result.tools.fileReadEnabled)
        assertEquals(false, result.tools.fileWriteEnabled)
        assertEquals(true, result.tools.fileEditEnabled)
        assertEquals(3, result.tools.shellAllowedExecutables.size)
        assertEquals("bash", result.tools.shellAllowedExecutables[0])
        assertEquals("sh", result.tools.shellAllowedExecutables[1])
        assertEquals("python3", result.tools.shellAllowedExecutables[2])
    }

    @Test
    fun `parseToml parses project block`() {
        val toml = """
            [project]
            enabled = true
            memory_k = 8
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(true, result.project.enabled)
        assertEquals(8, result.project.memoryK)
    }

    @Test
    fun `parseToml project defaults when block absent`() {
        val toml = """
            [server]
            host = "localhost"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(false, result.project.enabled)
        assertEquals(5, result.project.memoryK)
    }

    @Test
    fun `parseToml parses JSON array shell_allowed_executables`() {
        val toml = """
            [tools]
            shell_allowed_executables = ["bash", "sh", "node", "python3"]
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(4, result.tools.shellAllowedExecutables.size)
        assertEquals("bash", result.tools.shellAllowedExecutables[0])
        assertEquals("sh", result.tools.shellAllowedExecutables[1])
        assertEquals("node", result.tools.shellAllowedExecutables[2])
        assertEquals("python3", result.tools.shellAllowedExecutables[3])
    }

    @Test
    fun `parseToml parses old flat tool enable flags for backward compat`() {
        val toml = """
            [tools]
            shell_enabled = false
            file_read_enabled = true
            file_write_enabled = false
            file_edit_enabled = true
            shell_allowed_executables = "bash,sh,python3"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(false, result.tools.shellEnabled)
        assertEquals(true, result.tools.fileReadEnabled)
        assertEquals(false, result.tools.fileWriteEnabled)
        assertEquals(true, result.tools.fileEditEnabled)
    }

    @Test
    fun `parseToml parses capabilities as CSV string for backward compat`() {
        val toml = """
            [server]
            host = "localhost"

            [[providers]]
            id = "legacy-llama"
            type = "llamacpp"
            model_id = "mistral-7b"
            capabilities = "General=1.0,Coding=0.7,Reasoning=0.6"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(1, result.providers.size)
        assertEquals(3, result.providers[0].capabilities.size)
        assertEquals(1.0, result.providers[0].capabilities["General"])
        assertEquals(0.7, result.providers[0].capabilities["Coding"])
    }

    @Test
    fun `parseToml parses router embedder and l3 config`() {
        val toml = """
            [router.embedder]
            backend = "llamacpp"
            dimension = 1536
            url = "http://localhost:11000"
            model_id = "nomic-embed-text"

            [router.l3]
            backend = "turbovec"
            persist_path = "/tmp/router/l3.tq"
            python_executable = "python3"
            dim = 1536
            bit_width = 4
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals("llamacpp", result.talkie.embedder.backend)
        assertEquals(1536, result.talkie.embedder.dimension)
        assertEquals("http://localhost:11000", result.talkie.embedder.url)
        assertEquals("nomic-embed-text", result.talkie.embedder.modelId)

        assertEquals("turbovec", result.talkie.l3.backend)
        assertEquals("/tmp/router/l3.tq", result.talkie.l3.persistPath)
        assertEquals("python3", result.talkie.l3.pythonExecutable)
        assertEquals(1536, result.talkie.l3.dim)
        assertEquals(4, result.talkie.l3.bitWidth)
    }

    @Test
    fun `parseToml applies router defaults when sections absent`() {
        val toml = """
            [server]
            host = "localhost"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals("noop", result.talkie.embedder.backend)
        assertEquals(1536, result.talkie.embedder.dimension)
        assertEquals(null, result.talkie.embedder.url)
        assertEquals(null, result.talkie.embedder.modelId)

        assertEquals("in_memory", result.talkie.l3.backend)
        assertEquals(null, result.talkie.l3.persistPath)
        assertEquals("python3", result.talkie.l3.pythonExecutable)
        assertEquals(1536, result.talkie.l3.dim)
        assertEquals(4, result.talkie.l3.bitWidth)
    }

    @Test
    fun `parseToml parses models array-of-tables`() {
        val toml = """
            [models]
            default_model = "mistral-7b"
            llama_server_bin = "/usr/local/bin/llama-server"
            host = "127.0.0.1"
            port = 10001

            [[models]]
            id = "mistral-7b"
            model_path = "/models/mistral-7b.gguf"
            context_size = 4096
            capabilities = { General = 1.0, Coding = 0.7, Reasoning = 0.6 }

            [[models]]
            id = "codellama-7b"
            model_path = "/models/codellama-7b.gguf"
            context_size = 8192
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(2, result.models.size)

        assertEquals("mistral-7b", result.models[0].id)
        assertEquals("/models/mistral-7b.gguf", result.models[0].modelPath)
        assertEquals(4096, result.models[0].contextSize)
        assertEquals(3, result.models[0].capabilities.size)
        assertEquals(1.0, result.models[0].capabilities["General"])
        assertEquals(0.7, result.models[0].capabilities["Coding"])
        assertEquals(0.6, result.models[0].capabilities["Reasoning"])

        assertEquals("codellama-7b", result.models[1].id)
        assertEquals("/models/codellama-7b.gguf", result.models[1].modelPath)
        assertEquals(8192, result.models[1].contextSize)
        assertEquals(0, result.models[1].capabilities.size)

        assertEquals("mistral-7b", result.modelsSettings.defaultModel)
        assertEquals("/usr/local/bin/llama-server", result.modelsSettings.llamaServerBin)
        assertEquals("127.0.0.1", result.modelsSettings.host)
        assertEquals(10001, result.modelsSettings.port)
    }

    @Test
    fun `parseToml parses mcp array-of-tables with command list and env`() {
        val toml = """
            [[mcp]]
            id = "codebase-memory"
            command = ["codebase-memory-mcp", "serve"]
            env = { RUST_LOG = "info" }

            [[mcp]]
            id = "other"
            command = ["other-server"]
        """.trimIndent()

        val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(2, result.mcp.size)
        assertEquals("codebase-memory", result.mcp[0].id)
        assertEquals(listOf("codebase-memory-mcp", "serve"), result.mcp[0].command)
        assertEquals("info", result.mcp[0].env["RUST_LOG"])
        assertEquals("T2", result.mcp[0].tier)
        assertEquals(listOf("other-server"), result.mcp[1].command)
    }

    @Test
    fun `parseToml returns empty models list and default modelsSettings when sections absent`() {
        val toml = """
            [server]
            host = "localhost"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(0, result.models.size)
        assertEquals(null, result.modelsSettings.defaultModel)
        assertEquals("llama-server", result.modelsSettings.llamaServerBin)
        assertEquals("127.0.0.1", result.modelsSettings.host)
        assertEquals(10000, result.modelsSettings.port)
    }

    @Test
    fun `parseToml parses models alongside providers`() {
        val toml = """
            [[providers]]
            id = "remote-provider"
            type = "llamacpp"
            model_id = "remote-model"
            url = "http://192.168.1.10:10000"

            [[models]]
            id = "local-model"
            model_path = "/models/local.gguf"
        """.trimIndent()

        val loader = ConfigLoader::class.java
        val parseTomlMethod = loader.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(1, result.providers.size)
        assertEquals("remote-provider", result.providers[0].id)

        assertEquals(1, result.models.size)
        assertEquals("local-model", result.models[0].id)
        assertEquals("/models/local.gguf", result.models[0].modelPath)
        assertEquals(24_576, result.models[0].contextSize)
    }

    @Test
    fun `parseToml parses tools allowed_workspace_roots`() {
        val toml = """
            [tools]
            allowed_workspace_roots = ["/home/user/projects", "/tmp/work"]
        """.trimIndent()

        val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(2, result.tools.allowedWorkspaceRoots.size)
        assertEquals("/home/user/projects", result.tools.allowedWorkspaceRoots[0])
        assertEquals("/tmp/work", result.tools.allowedWorkspaceRoots[1])
    }

    @Test
    fun `parseToml parses router generation and narration knobs`() {
        val toml = """
            [router]
            conversation_keep_last = 8
            retrieval_k = 3
            token_budget = 8192

            [router.generation]
            temperature = 0.4
            top_p = 0.8
            max_tokens = 600

            [router.narration]
            temperature = 0.55
            top_p = 0.95
            max_tokens = 2048
            max_per_run = 25
        """.trimIndent()

        val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, toml) as CorrexConfig

        assertEquals(8, result.talkie.conversationKeepLast)
        assertEquals(3, result.talkie.retrievalK)
        assertEquals(8192, result.talkie.tokenBudget)
        assertEquals(0.4, result.talkie.generation.temperature)
        assertEquals(0.8, result.talkie.generation.topP)
        assertEquals(600, result.talkie.generation.maxTokens)
        assertEquals(0.55, result.talkie.narration.temperature)
        assertEquals(2048, result.talkie.narration.maxTokens)
        assertEquals(25, result.talkie.narration.maxPerRun)
    }

    @Test
    fun `parseToml uses router defaults when sections absent`() {
        val parseTomlMethod = ConfigLoader::class.java.getDeclaredMethod("parseToml", String::class.java)
        parseTomlMethod.isAccessible = true
        val result = parseTomlMethod.invoke(ConfigLoader, "[server]\nhost = \"x\"") as CorrexConfig

        assertEquals(6, result.talkie.conversationKeepLast)
        assertEquals(512, result.talkie.generation.maxTokens)
        assertEquals(4096, result.talkie.narration.maxTokens)
        assertEquals(100, result.talkie.narration.maxPerRun)
    }

    @Test
    fun `parseToml reads orchestration knobs`() {
        val toml = """
            [orchestration]
            stage_timeout_ms = 90000
            journal_compaction_token_threshold = 12000
        """.trimIndent()

        val result = ConfigLoader.parseTomlForTest(toml)

        assertEquals(90_000L, result.orchestration.stageTimeoutMs)
        assertEquals(12_000, result.orchestration.journalCompactionTokenThreshold)
    }

    @Test
    fun `parseToml orchestration defaults when absent`() {
        val result = ConfigLoader.parseTomlForTest("[server]\nport = 8080")
        assertEquals(180_000L, result.orchestration.stageTimeoutMs)
        assertEquals(2_000, result.orchestration.journalCompactionTokenThreshold)
    }

    @Test
    fun `parseToml parses router narration model_id`() {
        val toml = """
            [router.narration]
            model_id = "llama-cpp:phi-3-mini"
        """.trimIndent()

        val result = ConfigLoader.parseTomlForTest(toml)

        assertEquals("llama-cpp:phi-3-mini", result.talkie.narration.modelId)
    }

    @Test
    fun `parseToml narration model_id defaults to null when absent`() {
        val toml = """
            [router.narration]
            max_tokens = 2048
        """.trimIndent()

        val result = ConfigLoader.parseTomlForTest(toml)

        assertNull(result.talkie.narration.modelId)
    }
}
