import Foundation
import Testing

#if canImport(CryptoKit)
import CryptoKit
#else
import Crypto
#endif

@testable import NucleicProtocol

/// Wire + crypto contracts for the Covalence runner (docs/COVALENCE_RUNNER.md §5–6):
/// intelligence delegation, the sealed credential mesh, and the refresh-lease arbitration.
@Suite struct RunnerMessagesTests {
    private func roundTrip<T: Codable & Equatable>(_ value: T) throws -> T {
        try CBORDecoder().decode(T.self, from: CBOREncoder().encode(value))
    }

    // MARK: Capability flags (additive, decode-defaulted)

    @Test func wireCapabilitiesLegacyShapeDecodesRunnerFlagsFalse() throws {
        // A host that predates the runner omits both keys — they must read as false, exactly
        // the canFetchDiff pattern.
        let legacy = Data(#"{"canModifyToolInput":true,"allowAlwaysScopes":[]}"#.utf8)
        let caps = try JSONDecoder().decode(WireCapabilities.self, from: legacy)
        #expect(!caps.canDelegateIntelligence)
        #expect(!caps.canReceiveSealedCredentials)
    }

    @Test func wireCapabilitiesRunnerFlagsRoundTrip() throws {
        let caps = WireCapabilities(
            canModifyToolInput: false, allowAlwaysScopes: [],
            canDelegateIntelligence: true, canReceiveSealedCredentials: true)
        let back = try roundTrip(caps)
        #expect(back.canDelegateIntelligence)
        #expect(back.canReceiveSealedCredentials)
    }

    @Test func clientCapabilitiesLegacyShapeDecodesProvideIntelligenceFalse() throws {
        let legacy = Data(#"{"mesh":1,"canHost":true}"#.utf8)
        let caps = try JSONDecoder().decode(WireClientCapabilities.self, from: legacy)
        #expect(!caps.canProvideIntelligence)
        #expect(caps.canHost)
    }

    @Test func clientCapabilitiesProvideIntelligenceRoundTrips() throws {
        let caps = WireClientCapabilities(mesh: 1, canProvideIntelligence: true)
        #expect(try roundTrip(caps).canProvideIntelligence)
    }

    // MARK: Welcome.hostKind (a runner declares itself .cloud)

    @Test func welcomeCarriesHostKind() throws {
        let welcome = Welcome(
            grantedScope: .control, host: HostInfo(hostID: "h", hostName: "Cloud Runner"),
            capabilities: WireCapabilities(canModifyToolInput: false, allowAlwaysScopes: []),
            hostKind: PeerKind.cloud.rawValue)
        let back = try roundTrip(welcome)
        #expect(back.hostKind == "cloud")
        #expect(back.kind == .cloud)
    }

    @Test func welcomeWithoutHostKindDefaultsToMac() throws {
        // A host that predates the field (omits hostKind) must pin as .mac, not throw.
        let json =
            #"{"protocolVersion":1,"grantedScope":"control","host":{"hostID":"h","hostName":"Mac"},"#
            + #""capabilities":{"canModifyToolInput":true,"allowAlwaysScopes":[]}}"#
        let welcome = try JSONDecoder().decode(Welcome.self, from: Data(json.utf8))
        #expect(welcome.hostKind == nil)
        #expect(welcome.kind == .mac)
    }

    // MARK: Envelope wiring

    @Test func intelligenceRequestAndResultRoundTripThroughEnvelope() throws {
        let request = WireIntelligenceRequest(
            id: "req-1", kind: .classifyTurn, inputs: ["done — merged the branch"],
            context: nil, deadlineSeconds: 8)
        let host = HostMsg.intelligenceRequest(request)
        #expect(try roundTrip(host) == host)

        let result = WireIntelligenceResult(id: "req-1", outputs: ["completed"])
        let client = ClientMsg.intelligenceResult(result)
        #expect(try roundTrip(client) == client)
    }

    @Test func unknownIntelligenceKindDecodesToItsRawValue() throws {
        // Raw-string forward compat: a future kind never throws on an old device.
        let request = WireIntelligenceRequest(
            id: "r", kind: IntelligenceRequestKind(rawValue: "someFutureKind"), inputs: [])
        #expect(try roundTrip(request).kind.rawValue == "someFutureKind")
    }

    // MARK: Mesh AFM queue vocabulary (item 6)

    @Test func intelligenceRequestPriorityRoundTripsAndDefaultsNil() throws {
        let request = WireIntelligenceRequest(
            id: "r", kind: .sessionName, inputs: ["fix the login button"],
            deadlineSeconds: 20, priority: .interactive)
        #expect(try roundTrip(request).priority == .interactive)

        // A request from a build that predates the field reads as no priority (⇒ background).
        let legacy = Data(#"{"id":"r","kind":"classifyTurn","inputs":["done"]}"#.utf8)
        let decoded = try JSONDecoder().decode(WireIntelligenceRequest.self, from: legacy)
        #expect(decoded.priority == nil)
    }

    @Test func priorityRanksAndMobileRule() {
        // Ordering: interactive > bashSummary > completion > background.
        #expect(IntelligencePriority.interactive.rank > IntelligencePriority.bashSummary.rank)
        #expect(IntelligencePriority.bashSummary.rank > IntelligencePriority.completion.rank)
        #expect(IntelligencePriority.completion.rank > IntelligencePriority.background.rank)
        // The two highest tiers need desktop-class tok/s — never an iPhone/iPad.
        #expect(!IntelligencePriority.interactive.allowsMobileExecution)
        #expect(!IntelligencePriority.bashSummary.allowsMobileExecution)
        #expect(IntelligencePriority.completion.allowsMobileExecution)
        #expect(IntelligencePriority.background.allowsMobileExecution)
        // An unknown tier reads as background: lowest rank, mobile-allowed.
        let future = IntelligencePriority(rawValue: "someFutureTier")
        #expect(future.rank == IntelligencePriority.background.rank)
        #expect(future.allowsMobileExecution)
    }

    @Test func clientCapabilitiesIntelligenceProfileRoundTrips() throws {
        let caps = WireClientCapabilities(
            mesh: 1, canProvideIntelligence: true,
            intelligenceProfile: IntelligenceWorkerProfile(
                deviceClass: .desktop, chip: "Apple M4 Pro"))
        let back = try roundTrip(caps)
        #expect(back.intelligenceProfile?.deviceClass == .desktop)
        #expect(back.intelligenceProfile?.chip == "Apple M4 Pro")

        // Omitted profile (an older client) decodes nil, not a throw.
        let legacy = Data(#"{"mesh":1,"canProvideIntelligence":true}"#.utf8)
        let decoded = try JSONDecoder().decode(WireClientCapabilities.self, from: legacy)
        #expect(decoded.canProvideIntelligence)
        #expect(decoded.intelligenceProfile == nil)
    }

    @Test func appleSiliconHierarchy() {
        func rank(_ brand: String) -> Int { AppleSiliconChip.rank(brand: brand) }
        // Every M outranks every A (LLM decoding is bandwidth-bound; M-class memory wins).
        #expect(rank("Apple M1") > rank("Apple A18 Pro"))
        // Within the M series the tier dominates: an M1 Ultra's bandwidth beats an M4 base's.
        #expect(rank("Apple M1 Ultra") > rank("Apple M4 Pro"))
        #expect(rank("Apple M4 Max") > rank("Apple M4 Pro"))
        #expect(rank("Apple M4 Pro") > rank("Apple M4"))
        // Generation breaks ties within a tier.
        #expect(rank("Apple M4 Pro") > rank("Apple M1 Pro"))
        #expect(rank("Apple M4") > rank("Apple M1"))
        // A-series ranks by generation; "Pro" nudges up. "Apple"/"Max" must not parse as A/M.
        #expect(rank("Apple A18 Pro") > rank("Apple A18"))
        #expect(rank("Apple A18") > rank("Apple A15 Bionic"))
        // Intel/unknown/empty rank 0 — only ever chosen when nothing better is connected.
        #expect(rank("Intel(R) Core(TM) i9") == 0)
        #expect(rank("") == 0)
    }

    @Test func delegateTemplatesCoverEveryKnownKindAndRejectUnknown() {
        let known: [IntelligenceRequestKind] = [
            .classifyTurn, .sessionName, .summarizeSession, .summarizeTodos, .summarizeTodo,
            .triageTodos, .summarizeBashCommand, .mergeBashSummaries, .summarizeToolFamily,
            .updateTrunkSummary, .auditHostExecReason,
        ]
        for kind in known {
            let request = WireIntelligenceRequest(id: "r", kind: kind, inputs: ["a", "b"])
            #expect(IntelligenceDelegate.job(for: request) != nil, "no template for \(kind.rawValue)")
        }
        let future = WireIntelligenceRequest(
            id: "r", kind: IntelligenceRequestKind(rawValue: "someFutureKind"), inputs: [])
        #expect(IntelligenceDelegate.job(for: future) == nil)
    }

    @Test func classifyTurnTemplateKeepsItsAntiAgenticFraming() throws {
        // The macOS 27 beta 4 model answers AWAITING for plainly finished turns unless the
        // template forbids it from reasoning about what the agent should do NEXT — measured at
        // 12/17 without this framing versus 17/17 with it. A false AWAITING silently stops
        // autoship, so pin the clauses that earned the difference; they are easy to "tidy" away.
        let job = try #require(IntelligenceDelegate.job(
            for: WireIntelligenceRequest(id: "r", kind: .classifyTurn, inputs: ["all tests pass"])))
        #expect(job.instructions.contains("NOT an assistant"))
        #expect(job.instructions.contains("SHOULD do next"))
        #expect(job.instructions.contains("When unsure, answer DONE"))
        // The reply is matched by substring on AWAITING/DONE, so both tokens must be named.
        #expect(job.instructions.contains("AWAITING"))
        #expect(job.instructions.contains("DONE"))
    }

    @Test func sessionNameTemplateRoutesOnContext() throws {
        // Without a digest: the few-shot first-message template; with one: the rename template
        // grounded in what the agent actually did.
        let fresh = WireIntelligenceRequest(id: "r", kind: .sessionName, inputs: ["fix login"])
        let renamed = WireIntelligenceRequest(
            id: "r", kind: .sessionName, inputs: ["continue"], context: "edited auth.swift")
        let freshJob = try #require(IntelligenceDelegate.job(for: fresh))
        let renameJob = try #require(IntelligenceDelegate.job(for: renamed))
        #expect(freshJob.prompt.contains("fix login"))
        #expect(renameJob.prompt.contains("edited auth.swift"))
        #expect(freshJob.instructions != renameJob.instructions)
    }

    @Test func credentialVerbsRoundTripThroughEnvelope() throws {
        let need = WireCredentialNeed(
            kinds: [.claudeOAuth, .githubToken], sealingPublicKey: Data(repeating: 7, count: 32))
        #expect(try roundTrip(HostMsg.credentialNeeded(need)) == .credentialNeeded(need))

        let manifest = CredentialManifest(
            records: [
                CredentialRecordDescriptor(
                    kind: .codexAuth, updatedAt: Date(timeIntervalSince1970: 1_700_000_000),
                    provenanceDeviceID: "dev-a")
            ],
            leases: [
                CredentialRefreshLease(
                    kind: .claudeOAuth, holderDeviceID: "dev-a",
                    acquiredAt: Date(timeIntervalSince1970: 1_700_000_000),
                    expiresAt: Date(timeIntervalSince1970: 1_700_003_600))
            ])
        #expect(try roundTrip(ClientMsg.credentialManifest(manifest)) == .credentialManifest(manifest))

        let record = SealedCredentialRecord(
            kind: .githubToken, updatedAt: Date(timeIntervalSince1970: 1_700_000_000),
            box: SealedCredentialBox(
                ephemeralPublicKey: Data(repeating: 1, count: 32),
                ciphertext: Data(repeating: 2, count: 48)))
        let envelope = SealedCredentialEnvelope(records: [record])
        #expect(try roundTrip(ClientMsg.credentialProvision(envelope)) == .credentialProvision(envelope))
        #expect(try roundTrip(HostMsg.credentialUpdate(envelope)) == .credentialUpdate(envelope))
    }

    // MARK: Remote project creation (CLOUD_RUNTIME §4.3)

    @Test func wireCapabilitiesLegacyShapeDecodesCreateProjectsFalse() throws {
        // A host that predates the verb omits the key — it must read as false so a client
        // never sends the tag at a host that would throw on it.
        let legacy = Data(#"{"canModifyToolInput":true,"allowAlwaysScopes":[]}"#.utf8)
        let caps = try JSONDecoder().decode(WireCapabilities.self, from: legacy)
        #expect(!caps.canCreateProjects)
    }

    @Test func wireCapabilitiesCreateProjectsRoundTrips() throws {
        let caps = WireCapabilities(
            canModifyToolInput: false, allowAlwaysScopes: [], canCreateProjects: true)
        #expect(try roundTrip(caps).canCreateProjects)
    }

    @Test func createProjectRoundTripsThroughEnvelope() throws {
        let request = WireCreateProjectRequest(
            gitURL: "git@github.com:me/proj.git", branch: "main", name: "Proj")
        #expect(try roundTrip(ClientMsg.createProject(request)) == .createProject(request))
    }

    @Test func createProjectRequestWithoutOptionalsDecodes() throws {
        // A minimal request (URL only) from a build that never sets branch/name/requestID must
        // decode, not throw — the decode-defaulted-optionals discipline.
        let minimal = Data(#"{"gitURL":"https://github.com/me/proj"}"#.utf8)
        let request = try JSONDecoder().decode(WireCreateProjectRequest.self, from: minimal)
        #expect(request.gitURL == "https://github.com/me/proj")
        #expect(request.branch == nil)
        #expect(request.name == nil)
        #expect(request.requestID == nil)
    }

    @Test func runnerPoolCredentialRoundTripsThroughEnvelope() throws {
        let credential = WireRunnerPoolCredential(
            poolID: String(repeating: "f", count: 64), secret: "s3cret",
            url: "https://runner.dev.example", updatedAt: Date(timeIntervalSince1970: 1_700_000_000))
        #expect(try roundTrip(HostMsg.runnerPoolCredential(credential)) == .runnerPoolCredential(credential))

        // URL omitted (the production control plane) must decode, not throw.
        let minimal = Data(
            #"{"poolId":"abc","secret":"s","updatedAt":700000000}"#.utf8)
        let decoded = try JSONDecoder().decode(WireRunnerPoolCredential.self, from: minimal)
        #expect(decoded.url == nil)
        #expect(decoded.poolID == "abc")
    }

    @Test func projectCreatedRoundTripsThroughEnvelope() throws {
        let success = WireProjectCreated(
            requestID: "req-1", projectID: ProjectID(rawValue: UUID().uuidString), name: "Proj")
        #expect(try roundTrip(HostMsg.projectCreated(success)) == .projectCreated(success))

        let failure = WireProjectCreated(requestID: "req-2", error: "clone failed: no such repo")
        let back = try roundTrip(HostMsg.projectCreated(failure))
        #expect(back == .projectCreated(failure))
        if case .projectCreated(let outcome) = back {
            #expect(outcome.projectID == nil)
            #expect(outcome.error == "clone failed: no such repo")
        }
    }

    @Test func manifestWithoutLeasesDecodes() throws {
        // A manifest from a build that predates leases reads as lease-free, not a throw.
        let legacy = Data(#"{"records":[]}"#.utf8)
        let manifest = try JSONDecoder().decode(CredentialManifest.self, from: legacy)
        #expect(manifest.leases.isEmpty)
        // …and predates the device sealing key → nil, not a throw (item 5).
        #expect(manifest.sealingPublicKey == nil)
    }

    @Test func manifestCarriesDeviceSealingKey() throws {
        // The device's sealing key rides the manifest so a runner can mirror a rotation back
        // (`credentialUpdate`). Round-trips through the envelope.
        let manifest = CredentialManifest(
            records: [CredentialRecordDescriptor(
                kind: .claudeOAuth, updatedAt: Date(timeIntervalSince1970: 1_700_000_000),
                provenanceDeviceID: "dev-a")],
            sealingPublicKey: Data(repeating: 9, count: 32))
        let back = try roundTrip(ClientMsg.credentialManifest(manifest))
        #expect(back == .credentialManifest(manifest))
        if case .credentialManifest(let m) = back {
            #expect(m.sealingPublicKey == Data(repeating: 9, count: 32))
        }
    }

    // MARK: Sealed boxes

    @Test func sealedBoxRoundTrips() throws {
        let recipient = Curve25519.KeyAgreement.PrivateKey()
        let secret = Data("{\"claudeAiOauth\":{\"accessToken\":\"abc\"}}".utf8)
        let aad = Data("claude-oauth".utf8)
        let box = try SealedCredentialBox.seal(
            secret, to: recipient.publicKey.rawRepresentation, additionalData: aad)
        let opened = try box.open(with: recipient, additionalData: aad)
        #expect(opened == secret)
    }

    @Test func sealedBoxRejectsWrongRecipient() throws {
        let recipient = Curve25519.KeyAgreement.PrivateKey()
        let interloper = Curve25519.KeyAgreement.PrivateKey()
        let box = try SealedCredentialBox.seal(
            Data("secret".utf8), to: recipient.publicKey.rawRepresentation)
        #expect(throws: SealedCredentialBox.SealedCredentialError.decryptFailed) {
            _ = try box.open(with: interloper)
        }
    }

    @Test func sealedBoxBindsAdditionalData() throws {
        // A box sealed as one credential kind must not open as another (replay-as-github-token).
        let recipient = Curve25519.KeyAgreement.PrivateKey()
        let box = try SealedCredentialBox.seal(
            Data("secret".utf8), to: recipient.publicKey.rawRepresentation,
            additionalData: SealedCredentialRecord(
                kind: .claudeOAuth, updatedAt: Date(), box: .init(ephemeralPublicKey: Data(), ciphertext: Data())
            ).additionalData)
        let wrongKindAAD = Data(CredentialKind.githubToken.rawValue.utf8)
        #expect(throws: SealedCredentialBox.SealedCredentialError.decryptFailed) {
            _ = try box.open(with: recipient, additionalData: wrongKindAAD)
        }
    }

    @Test func sealedBoxRejectsTamperedCiphertext() throws {
        let recipient = Curve25519.KeyAgreement.PrivateKey()
        let box = try SealedCredentialBox.seal(
            Data("secret".utf8), to: recipient.publicKey.rawRepresentation)
        var flipped = box.ciphertext
        flipped[flipped.count - 1] ^= 0x01
        let tampered = SealedCredentialBox(
            ephemeralPublicKey: box.ephemeralPublicKey, ciphertext: flipped)
        #expect(throws: SealedCredentialBox.SealedCredentialError.decryptFailed) {
            _ = try tampered.open(with: recipient)
        }
    }

    @Test func sealedBoxRejectsMalformedRecipientKey() {
        #expect(throws: SealedCredentialBox.SealedCredentialError.malformedKey) {
            _ = try SealedCredentialBox.seal(Data("x".utf8), to: Data(repeating: 0, count: 16))
        }
    }

    // MARK: Refresh-lease arbitration

    private func lease(
        holder: String, acquired: TimeInterval, expires: TimeInterval
    ) -> CredentialRefreshLease {
        CredentialRefreshLease(
            kind: .claudeOAuth, holderDeviceID: holder,
            acquiredAt: Date(timeIntervalSince1970: acquired),
            expiresAt: Date(timeIntervalSince1970: expires))
    }

    @Test func liveLeaseBeatsExpiredLease() {
        let now = Date(timeIntervalSince1970: 1_000)
        let dead = lease(holder: "a", acquired: 0, expires: 500)
        let live = lease(holder: "b", acquired: 600, expires: 2_000)
        #expect(CredentialRefreshLease.merged(dead, live, at: now)?.holderDeviceID == "b")
        #expect(CredentialRefreshLease.merged(live, dead, at: now)?.holderDeviceID == "b")
    }

    @Test func earlierClaimKeepsTheLease() {
        // First writer wins — a newer claim must NOT steal a live lease, or two devices would
        // both refresh (and revoke each other's rotated tokens).
        let now = Date(timeIntervalSince1970: 1_000)
        let first = lease(holder: "a", acquired: 100, expires: 2_000)
        let second = lease(holder: "b", acquired: 200, expires: 3_000)
        #expect(CredentialRefreshLease.merged(first, second, at: now)?.holderDeviceID == "a")
        #expect(CredentialRefreshLease.merged(second, first, at: now)?.holderDeviceID == "a")
    }

    @Test func exactTieBreaksByDeviceID() {
        let now = Date(timeIntervalSince1970: 1_000)
        let a = lease(holder: "aaa", acquired: 100, expires: 2_000)
        let b = lease(holder: "bbb", acquired: 100, expires: 2_000)
        #expect(CredentialRefreshLease.merged(a, b, at: now)?.holderDeviceID == "aaa")
        #expect(CredentialRefreshLease.merged(b, a, at: now)?.holderDeviceID == "aaa")
    }

    @Test func expiredLeasesClearToNil() {
        let now = Date(timeIntervalSince1970: 10_000)
        let a = lease(holder: "a", acquired: 0, expires: 500)
        let b = lease(holder: "b", acquired: 100, expires: 900)
        #expect(CredentialRefreshLease.merged(a, b, at: now) == nil)
        #expect(CredentialRefreshLease.merged(a, nil, at: now) == nil)
        #expect(CredentialRefreshLease.merged(nil, nil, at: now) == nil)
    }

    // MARK: Mesh-wide credential deletion (tombstones)

    private func stone(
        _ kind: CredentialKind, deletedAt: TimeInterval, origin: String = "dev-a"
    ) -> CredentialTombstone {
        CredentialTombstone(
            kind: kind, deletedAt: Date(timeIntervalSince1970: deletedAt), originDeviceID: origin)
    }

    @Test func wireCapabilitiesLegacyShapeDecodesRevokeCredentialsFalse() throws {
        // A host that predates deletion omits the key — it must read as false so a client
        // never sends `credentialRevoke` at a host that would throw on the unknown tag.
        let legacy = Data(#"{"canModifyToolInput":true,"allowAlwaysScopes":[]}"#.utf8)
        let caps = try JSONDecoder().decode(WireCapabilities.self, from: legacy)
        #expect(!caps.canRevokeCredentials)
    }

    @Test func wireCapabilitiesRevokeCredentialsRoundTrips() throws {
        let caps = WireCapabilities(
            canModifyToolInput: false, allowAlwaysScopes: [], canRevokeCredentials: true)
        #expect(try roundTrip(caps).canRevokeCredentials)
    }

    @Test func credentialRevokeVerbsRoundTripThroughEnvelope() throws {
        let stones = [
            stone(.claudeOAuth, deletedAt: 1_700_000_000),
            stone(.openAIAPIKey, deletedAt: 1_700_000_100, origin: "dev-b"),
        ]
        #expect(try roundTrip(ClientMsg.credentialRevoke(stones)) == .credentialRevoke(stones))
        #expect(try roundTrip(HostMsg.credentialRevoked(stones)) == .credentialRevoked(stones))
    }

    @Test func tombstoneWithoutOriginDecodes() throws {
        // The origin is informational — a minimal stone (kind + deletedAt) must decode, not
        // throw (the decode-defaulted-optionals discipline).
        let minimal = Data(#"{"kind":"claude-oauth","deletedAt":700000000}"#.utf8)
        let decoded = try JSONDecoder().decode(CredentialTombstone.self, from: minimal)
        #expect(decoded.kind == .claudeOAuth)
        #expect(decoded.originDeviceID.isEmpty)
    }

    @Test func manifestWithoutTombstonesDecodes() throws {
        // A manifest from a build that predates deletion reads as tombstone-free, not a throw.
        let legacy = Data(#"{"records":[]}"#.utf8)
        let manifest = try JSONDecoder().decode(CredentialManifest.self, from: legacy)
        #expect(manifest.tombstones.isEmpty)
    }

    @Test func manifestCarriesTombstones() throws {
        let manifest = CredentialManifest(
            records: [], tombstones: [stone(.codexAuth, deletedAt: 1_700_000_000)])
        let back = try roundTrip(ClientMsg.credentialManifest(manifest))
        #expect(back == .credentialManifest(manifest))
    }

    @Test func tombstoneMergePicksLaterDeletion() {
        let early = stone(.claudeOAuth, deletedAt: 1_000, origin: "zzz")
        let late = stone(.claudeOAuth, deletedAt: 2_000, origin: "aaa")
        #expect(CredentialTombstone.merged(early, late) == late)
        #expect(CredentialTombstone.merged(late, early) == late)
        // Exact tie breaks by the lower origin id, both orders.
        let tieA = stone(.claudeOAuth, deletedAt: 1_000, origin: "aaa")
        #expect(CredentialTombstone.merged(early, tieA) == tieA)
        #expect(CredentialTombstone.merged(tieA, early) == tieA)
        // Set merge is per-kind.
        let other = stone(.codexAuth, deletedAt: 500)
        let merged = CredentialTombstone.merged([early, other], [late])
        #expect(merged.count == 2)
        #expect(merged.contains(late) && merged.contains(other))
    }

    @Test func tombstoneSuppressesUpToDeletionInstant() {
        let deletion = stone(.claudeOAuth, deletedAt: 1_000)
        // At-or-before the deletion stays dead — re-provisioning the deleted revision must not
        // resurrect it; only a strictly newer login wins again.
        #expect(deletion.suppresses(recordUpdatedAt: Date(timeIntervalSince1970: 999)))
        #expect(deletion.suppresses(recordUpdatedAt: Date(timeIntervalSince1970: 1_000)))
        #expect(!deletion.suppresses(recordUpdatedAt: Date(timeIntervalSince1970: 1_001)))
    }

    @Test func tombstoneAbsorbsAFresherLocalCopy() {
        // Claude stamps records with the token's FUTURE expiresAt, so a deletion minted "now"
        // wouldn't cover the very copy it deletes — the holder bumps the stone past its copy's
        // stamp before re-gossiping.
        let deletion = stone(.claudeOAuth, deletedAt: 1_000)
        let bumped = deletion.absorbing(freshness: Date(timeIntervalSince1970: 5_000))
        #expect(bumped.suppresses(recordUpdatedAt: Date(timeIntervalSince1970: 5_000)))
        // A copy older than the stone needs no bump.
        #expect(deletion.absorbing(freshness: Date(timeIntervalSince1970: 500)) == deletion)
        // The bump is monotonic: merging the bumped stone anywhere wins over the original.
        #expect(CredentialTombstone.merged(deletion, bumped) == bumped)
    }

    // MARK: Shared freshness comparators (the phone reconciles with the Mac's exact rules)

    @Test func claudeComparatorPicksLaterExpiry() {
        let old = #"{"claudeAiOauth":{"accessToken":"a","refreshToken":"r","expiresAt":1000}}"#
        let new = #"{"claudeAiOauth":{"accessToken":"b","refreshToken":"r2","expiresAt":2000}}"#
        #expect(ClaudeCredentialFormat.shouldReplace(candidate: new, current: old))
        #expect(!ClaudeCredentialFormat.shouldReplace(candidate: old, current: new))
        #expect(!ClaudeCredentialFormat.shouldReplace(candidate: old, current: old))  // identical
        #expect(ClaudeCredentialFormat.shouldReplace(candidate: new, current: nil))   // seed
        // Invalid candidates never land; an unparseable current is never overwritten.
        #expect(!ClaudeCredentialFormat.shouldReplace(candidate: "{}", current: nil))
        #expect(!ClaudeCredentialFormat.shouldReplace(candidate: new, current: "not json"))
        #expect(ClaudeCredentialFormat.expiresAt(new) == 2000)
    }

    @Test func codexComparatorPicksLaterRefresh() {
        let old = #"{"tokens":{"refresh_token":"r"},"last_refresh":"2026-07-09T10:00:00Z"}"#
        let new = #"{"tokens":{"refresh_token":"r2"},"last_refresh":"2026-07-09T11:00:00.5Z"}"#
        #expect(CodexCredentialFormat.shouldReplace(candidate: new, current: old))
        #expect(!CodexCredentialFormat.shouldReplace(candidate: old, current: new))
        #expect(CodexCredentialFormat.shouldReplace(candidate: new, current: nil))     // seed
        #expect(CodexCredentialFormat.shouldReplace(candidate: new, current: "junk"))  // stale current
        // API-key-only auth.json (no refresh lineage) is never a valid login to propagate.
        #expect(!CodexCredentialFormat.shouldReplace(
            candidate: #"{"OPENAI_API_KEY":"sk-x"}"#, current: nil))
        #expect(CodexCredentialFormat.lastRefresh(old) != nil)
    }
}
