import Foundation
import Testing

@testable import NucleicProtocol

/// Wire coverage for remote agent sign-in (docs/REMOTE_AGENT_LOGIN.md): the three client verbs,
/// the three host replies, and the tolerance contracts (unknown capture modes and status fields
/// decode-default instead of throwing).
@Suite struct AgentLoginMessageTests {
    private func roundTrip<T: Codable & Equatable>(_ value: T) throws -> T {
        try CBORDecoder().decode(T.self, from: CBOREncoder().encode(value))
    }

    @Test func clientVerbsRoundTrip() throws {
        let msgs: [ClientMsg] = [
            .agentLoginBegin(WireAgentLoginBegin(
                requestID: "r1", provider: .claude, boundLoopbackPort: 49_312)),
            .agentLoginBegin(WireAgentLoginBegin(requestID: "r2", provider: .codex)),
            .agentLoginCallback(WireAgentLoginCallback(
                requestID: "r1", code: "authcode", state: "st")),
            .agentLoginCallback(WireAgentLoginCallback(requestID: "r3", code: "code#state")),
            .agentLoginCancel("r1"),
        ]
        for msg in msgs {
            #expect(try roundTrip(msg) == msg)
        }
    }

    @Test func hostRepliesRoundTrip() throws {
        let msgs: [HostMsg] = [
            .agentLoginChallenge(WireAgentLoginChallenge(
                requestID: "r1",
                authorizeURL: "https://claude.ai/oauth/authorize?x=y",
                capture: .loopback(port: 1455, path: "/auth/callback"),
                expiresAt: Date(timeIntervalSince1970: 1_700_000_000))),
            .agentLoginChallenge(WireAgentLoginChallenge(
                requestID: "r2",
                authorizeURL: "https://claude.ai/oauth/authorize",
                capture: .pasteCode,
                expiresAt: Date(timeIntervalSince1970: 1_700_000_600))),
            .agentLoginResult(WireAgentLoginResult(requestID: "r1")),
            .agentLoginResult(WireAgentLoginResult(
                requestID: "r2", error: "port busy", accountLabel: nil)),
            .agentAuthStatus([
                WireProviderAuthStatus(
                    provider: .claude, name: "Claude Code", installed: true,
                    authenticated: false, canBrokerLogin: true),
                WireProviderAuthStatus(
                    provider: AgentLoginProvider(rawValue: "grok"), name: "Grok",
                    installed: false, authenticated: false),
            ]),
        ]
        for msg in msgs {
            #expect(try roundTrip(msg) == msg)
        }
    }

    @Test func resultSuccessIsAbsentError() {
        #expect(WireAgentLoginResult(requestID: "r").succeeded)
        #expect(!WireAgentLoginResult(requestID: "r", error: "nope").succeeded)
    }

    @Test func unknownCaptureModeDecodesInsteadOfThrowing() throws {
        // A future host may invent a capture shape; the device must decode the challenge (and
        // fail the attempt gracefully) rather than drop the frame as undecodable.
        let future = AgentLoginCapture(mode: "webview-magic", port: nil, path: nil)
        let decoded = try roundTrip(future)
        #expect(decoded.mode == "webview-magic")
        #expect(decoded != .pasteCode)
    }

    @Test func statusToleratesMissingFields() throws {
        // Only `provider` is required on the wire; everything else decode-defaults, so a newer
        // peer can slim or extend the record without breaking an older one.
        struct Minimal: Codable { let provider: AgentLoginProvider }
        let data = try CBOREncoder().encode(Minimal(provider: .codex))
        let decoded = try CBORDecoder().decode(WireProviderAuthStatus.self, from: data)
        #expect(decoded.provider == .codex)
        #expect(decoded.name.isEmpty)
        #expect(!decoded.installed)
        #expect(!decoded.authenticated)
        #expect(!decoded.canBrokerLogin)
        #expect(decoded.method == nil)
        #expect(decoded.accountLabel == nil)
    }

    @Test func capabilityBitDefaultsFalseForOlderHosts() throws {
        // An older host's Welcome omits the key — the device must decode `false` and never
        // send the new verbs at it.
        let legacy = WireCapabilities(canModifyToolInput: true, allowAlwaysScopes: [])
        let decoded = try roundTrip(legacy)
        #expect(!decoded.canBrokerAgentLogin)

        let modern = WireCapabilities(
            canModifyToolInput: true, allowAlwaysScopes: [], canBrokerAgentLogin: true)
        #expect(try roundTrip(modern).canBrokerAgentLogin)
    }
}
