import Foundation
import Testing

@testable import NucleicCore

/// `BackendID.forModel` is the single source of truth that lets the model picker double as
/// the backend selector — and, in the UI layer, drives the per-model effort cap + the
/// "Reasoning"/"Effort" label. Lock the mapping.
@Suite struct BackendRoutingTests {
    @Test func claudeModelsRouteToClaudeCode() {
        #expect(BackendID.forModel("claude-fable-5") == .claudeCode)
        #expect(BackendID.forModel("claude-opus-4-8[1m]") == .claudeCode)
        #expect(BackendID.forModel("claude-opus-4-8") == .claudeCode)
        #expect(BackendID.forModel("claude-sonnet-4-6") == .claudeCode)
        #expect(BackendID.forModel("claude-haiku-4-5") == .claudeCode)
    }

    @Test func gptAndCodexModelsRouteToCodex() {
        #expect(BackendID.forModel("gpt-5.5") == .codex)
        #expect(BackendID.forModel("gpt-5.4-mini") == .codex)
        #expect(BackendID.forModel("o3") == .codex)
        #expect(BackendID.forModel("gpt-5-codex") == .codex)
        #expect(BackendID.forModel("GPT-5.5") == .codex)  // case-insensitive
    }

    @Test func grokModelsRouteToGrok() {
        #expect(BackendID.forModel("grok-build") == .grok)
        #expect(BackendID.forModel("GROK-BUILD") == .grok)  // case-insensitive
    }

    @Test func acpWrapperModelsRouteToTheirBackends() {
        #expect(BackendID.forModel("opencode") == .opencode)
        // `opencode` and `openclaw` share an `openc` stem — `forModel` matches by prefix, so guard
        // that neither SKU is swallowed by the other's branch.
        #expect(BackendID.forModel("openclaw") == .openclaw)
        #expect(BackendID.forModel("OpenClaw") == .openclaw)  // case-insensitive
        #expect(BackendID.forModel("hermes") == .hermes)
        #expect(BackendID.forModel("cursor-agent") == .cursorAgent)
        #expect(BackendID.forModel("Cursor-Agent") == .cursorAgent)  // case-insensitive
        #expect(BackendID.forModel("acp-agent") == .acp)
    }

    /// Every offered SKU must resolve to a backend, and each ACP wrapper SKU's `ACPAgent` profile
    /// must round-trip back to the same backend — the two sources of truth agree.
    @Test func everyACPAgentProfileRoundTrips() {
        for agent in ACPAgent.configured {
            #expect(BackendID.forModel(agent.sku) == agent.backend)
            #expect(ACPAgent.forBackend(agent.backend)?.sku == agent.sku)
        }
    }

    @Test func acpWrapperAgentsAreNotCodexFamily() {
        for backend in [BackendID.grok, .opencode, .openclaw, .hermes, .cursorAgent, .acp] {
            #expect(backend.isCodexFamily == false)
        }
    }

    @Test func genericACPArgumentsAreSplitWithoutAShell() {
        #expect(ACPAgent.splitArguments(#"agent --stdio "profile name" escaped\ value"#) ==
            ["agent", "--stdio", "profile name", "escaped value"])
    }

    @Test func genericACPProfileUsesDefaultsAndEnvironmentOverrides() {
        let suiteName = "ACPAgentTests-" + UUID().uuidString
        let defaults = UserDefaults(suiteName: suiteName)!
        defer { defaults.removePersistentDomain(forName: suiteName) }
        defaults.set("/usr/local/bin/my-agent", forKey: ACPAgent.genericExecutableDefaultsKey)
        defaults.set("serve --stdio", forKey: ACPAgent.genericArgumentsDefaultsKey)

        let stored = ACPAgent.generic(defaults: defaults, environment: [:])
        #expect(stored.backend == .acp)
        #expect(stored.executable == "/usr/local/bin/my-agent")
        #expect(stored.launchArgs == ["serve", "--stdio"])
        #expect(stored.authenticationIsManagedByAgent)
        #expect(!stored.supportsContainerizedRuns)

        let overridden = ACPAgent.generic(defaults: defaults, environment: [
            ACPAgent.genericExecutableEnvironmentKey: "env-agent",
            ACPAgent.genericArgumentsEnvironmentKey: "acp --profile \"work profile\"",
        ])
        #expect(overridden.executable == "env-agent")
        #expect(overridden.launchArgs == ["acp", "--profile", "work profile"])
        #expect(ACPAgent.grok.supportsContainerizedRuns)
    }

    @Test func unknownModelIsUnclassified() {
        #expect(BackendID.forModel("mystery-model") == nil)
        #expect(BackendID.forModel(nil) == nil)
    }
}
