import Testing

@testable import NucleicCore

/// Provider approval callbacks remain client-reviewed; Nucleic owns policy and presentation.
@Suite struct CodexAppServerBackendTests {
    @Test func autoModeStillRoutesReviewToNucleic() {
        #expect(CodexAppServerBackend.approvalsReviewer(autoApprove: true) == "user")
    }

    @Test func resumePreservesPlatformToolCapabilities() {
        let resume = ResumeSpec(
            sessionID: SessionID(rawValue: "resume-tools"), backendSessionID: "thread-1",
            worktree: "/repo", allowHostExec: true, allowMacVMExec: true,
            allowMacVMComputer: true, allowLinuxVMExec: true,
            allowLinuxVMComputer: true, allowAgentContainers: true)
        let run = CodexAppServerBackend.runSpec(resuming: resume)
        #expect(run.allowHostExec)
        #expect(run.allowMacVMExec)
        #expect(run.allowMacVMComputer)
        #expect(run.allowLinuxVMExec)
        #expect(run.allowLinuxVMComputer)
        #expect(run.allowAgentContainers)
    }

    @Test func commonAutoPolicyNeverApprovesDestructiveUnknownOrQuestions() {
        #expect(NucleicApprovalPolicy.shouldAutoApprove(autoApprove: true, risk: .write))
        #expect(!NucleicApprovalPolicy.shouldAutoApprove(autoApprove: true, risk: .destructive))
        #expect(!NucleicApprovalPolicy.shouldAutoApprove(autoApprove: true, risk: .unknown))
        #expect(!NucleicApprovalPolicy.shouldAutoApprove(
            autoApprove: true, risk: .write, requiresExplicitUserDecision: true))
        #expect(!NucleicApprovalPolicy.shouldAutoApprove(autoApprove: false, risk: .write))
    }

    @Test func interactiveModeSelectsUserReviewer() {
        #expect(CodexAppServerBackend.approvalsReviewer(autoApprove: false) == "user")
    }

    @Test func acceptsSupportedMaxAndSolProWireEfforts() {
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "max", model: "gpt-5.6-sol", codexPro: false) == "max")
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "max", model: "gpt-5.6-terra", codexPro: false) == "max")
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "max", model: "gpt-5.5", codexPro: true) == nil)
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "ultra", model: "gpt-5.6-sol", codexPro: true) == "ultra")
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "ultra", model: "gpt-5.6-sol", codexPro: false) == nil)
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "ultra", model: "gpt-5.6-terra", codexPro: true) == nil)
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "pro", model: "gpt-5.6-sol", codexPro: true) == nil)
        #expect(CodexAppServerBackend.acceptedReasoningEffort(
            "unknown", model: "gpt-5.6-sol", codexPro: true) == nil)
    }

    @Test func classifiesConcreteSandboxDNSFailureForNetworkRecovery() {
        let captured: JSONValue = [
            "error": [
                "message": "Reconnecting... 2/5",
                "additionalDetails": .string(
                    "stream disconnected before completion: "
                    + "failed to lookup address information: Try again"),
            ],
            "willRetry": true,
        ]
        #expect(CodexAppServerBackend.reportsSandboxDNSFailure(
            method: "error", params: captured))

        // The same words in an unrelated notification are not a recovery signal.
        #expect(!CodexAppServerBackend.reportsSandboxDNSFailure(
            method: "item/completed", params: captured))
    }

    @Test func genericProviderDisconnectDoesNotCycleSandboxNetworking() {
        let disconnected: JSONValue = [
            "error": [
                "message": "Reconnecting... 3/5",
                "additionalDetails": .string(
                    "stream disconnected before completion: error sending request for url "
                    + "(https://chatgpt.com/backend-api/codex/responses)"),
            ],
            "willRetry": true,
        ]
        #expect(!CodexAppServerBackend.reportsSandboxDNSFailure(
            method: "error", params: disconnected))
        #expect(!CodexAppServerBackend.reportsSandboxDNSFailure(
            method: "error", params: [
                "error": ["additionalDetails": "upstream returned HTTP 503"],
            ]))
    }

    // MARK: - Nucleic-MCP auto-allow (CodexGateOwnership)

    private func request(_ method: String, _ params: [String: JSONValue])
        -> JSONRPCConnection.InboundRequest
    {
        JSONRPCConnection.InboundRequest(id: .string("1"), method: method, params: .object(params))
    }

    @Test func topLevelServerAndToolIdentityStillMatch() {
        #expect(CodexAppServerBackend.concernsNucleicMCP(
            request("item/tool/requestApproval", ["server": .string("nucleic")])))
        #expect(CodexAppServerBackend.concernsNucleicMCP(
            request("item/tool/requestApproval", ["toolName": .string("mcp__nucleic__mac_vm_exec")])))
    }

    /// A guardian permission request identifies the call *nested*, under the requested-permission
    /// entry rather than at the top level — the shape a top-level-only check missed, which is how
    /// Nucleic's own VM tools ended up prompting or failing instead of being auto-allowed.
    @Test func nestedGuardianPermissionPayloadMatches() {
        let nested = request(
            "permissions/requestApproval",
            [
                "threadId": .string("t1"),
                "permissions": .array([
                    .object([
                        "mcpToolCall": .object([
                            "toolName": .string("mcp__nucleic__linux_container"),
                            "connectorName": .string("nucleic"),
                        ])
                    ])
                ]),
            ])
        #expect(CodexAppServerBackend.concernsNucleicMCP(nested))

        // Same shape in the core's snake_case spelling.
        let snake = request(
            "permissions/requestApproval",
            ["permissions": .array([.object(["mcp_tool_call": .object([
                "tool_name": .string("mcp__nucleic__mac_vm_computer"),
                "connector_name": .string("nucleic"),
            ])])])])
        #expect(CodexAppServerBackend.concernsNucleicMCP(snake))
    }

    /// The recursive match must not auto-allow an unrelated approval. This project is itself named
    /// "nucleic", so a bare `name: "nucleic"` buried in a command approval's payload must NOT count
    /// — only a server/connector key, or a `mcp__nucleic__`-qualified tool name, may.
    @Test func unrelatedApprovalNamingTheProjectIsNotAutoAllowed() {
        let commandApproval = request(
            "item/commandExecution/requestApproval",
            [
                "itemId": .string("c1"),
                "command": .string("rm -rf build"),
                "workspace": .object(["name": .string("nucleic")]),
            ])
        #expect(!CodexAppServerBackend.concernsNucleicMCP(commandApproval))

        let otherServer = request(
            "permissions/requestApproval",
            ["permissions": .array([.object(["mcpToolCall": .object([
                "toolName": .string("mcp__github__create_issue"),
                "connectorName": .string("github"),
            ])])])])
        #expect(!CodexAppServerBackend.concernsNucleicMCP(otherServer))
    }
}
