import Foundation
import Testing

@testable import NucleicCore
import NucleicProtocol

/// CARBON_SHARDING §6.3: the manifest's canonical CBOR encoding is deterministic and its
/// field names/order are frozen by these tests. A changed frozen hash means the wire format
/// moved — bump `v` and write a migration story, don't retune the test.
@Suite("Carbon manifest")
struct CarbonManifestTests {
    private let projectUUID = UUID(uuidString: "8A6E2D3C-1B5F-4E7A-9C0D-2F4B6A8E1C3D")!

    private func fullManifest() -> CarbonManifest {
        CarbonManifest(
            project: .init(
                uuid: projectUUID, rootCommitSHA: "aaf00d1234deadbeefcafe000111222333444555",
                normalizedRemote: "github.com/example/repo", cloneURL: "git@github.com:example/repo.git",
                localOnly: false),
            stream: .worktreeSnapshot,
            sessionID: "sess-1",
            branch: "nucleic/keen-fox",
            baseSHA: "bbbb000011112222333344445555666677778888",
            ownerDeviceID: "host-A",
            ownerGeneration: 3,
            snapshotSeq: 42,
            parentManifest: "cafe000000000000000000000000000000000000000000000000000000000001",
            branchTipSHA: "cccc000011112222333344445555666677778888",
            wtCommitSHA: "dddd000011112222333344445555666677778888",
            worktreeTreeSHA: "eeee000011112222333344445555666677778888",
            idxCommitSHA: "ffff000011112222333344445555666677778888",
            indexTreeSHA: "abab000011112222333344445555666677778888",
            indexUnavailable: false,
            payloadKind: .gitPack,
            segmentOffset: nil,
            backendSessionID: nil,
            shards: [
                .init(id: String(repeating: "0", count: 63) + "1", byteCount: 2_097_152),
                .init(id: String(repeating: "0", count: 63) + "2", byteCount: 1024),
            ],
            createdAtMs: 1_752_000_000_000)
    }

    private func minimalManifest() -> CarbonManifest {
        CarbonManifest(
            project: .init(uuid: projectUUID, localOnly: true),
            stream: .history,
            ownerDeviceID: "host-A",
            ownerGeneration: 1,
            snapshotSeq: 1,
            payloadKind: .gitPack,
            shards: [],
            createdAtMs: 1_752_000_000_000)
    }

    @Test func canonicalRoundTrip() throws {
        for manifest in [fullManifest(), minimalManifest()] {
            let bytes = try manifest.canonicalCBOR()
            let decoded = try CarbonManifest.decode(canonical: bytes)
            #expect(decoded == manifest)
            // Determinism: re-encoding the decoded value is byte-identical.
            #expect(try decoded.canonicalCBOR() == bytes)
        }
    }

    @Test func encodingIsDeterministicAcrossInstances() throws {
        #expect(try fullManifest().canonicalCBOR() == fullManifest().canonicalCBOR())
    }

    @Test func sealedRoundTripAndManifestID() throws {
        let key = try CarbonCrypto.ProjectKey(bytes: Data((0..<32).map { UInt8($0) }))
        let crypto = CarbonCrypto(projectKey: key)
        let manifest = fullManifest()
        let sealed = try manifest.sealed(with: crypto)
        #expect(sealed.id == CarbonCrypto.blobID(sealed.ciphertext))
        #expect(try CarbonManifest.open(sealed.ciphertext, with: crypto) == manifest)
    }

    @Test func headDerivation() {
        let manifest = fullManifest()
        let head = manifest.head(manifestID: "abc123")
        #expect(head.key == CarbonStreamKey(
            projectUUID: projectUUID, stream: .worktreeSnapshot, sessionID: "sess-1"))
        #expect(head.manifestID == "abc123")
        #expect(head.ownerGeneration == 3)
        #expect(head.snapshotSeq == 42)
        #expect(head.keyMissing == false)
    }

    /// The frozen canonical bytes (as a SHA-256 of the encoding, which pins every field name,
    /// the sorted-key order, and the omit-nil convention). Captured on macOS; Linux must match.
    @Test func canonicalBytesAreFrozen() throws {
        let fullSHA = CarbonCrypto.blobID(try fullManifest().canonicalCBOR())
        let minimalSHA = CarbonCrypto.blobID(try minimalManifest().canonicalCBOR())
        #expect(fullSHA == CarbonManifestFrozen.fullManifestCanonicalSHA)
        #expect(minimalSHA == CarbonManifestFrozen.minimalManifestCanonicalSHA)
    }
}

enum CarbonManifestFrozen {
    static let fullManifestCanonicalSHA =
        "3caf4bf01a7eb0bcf3cd0335d007eb45e56a07d766982fef6d33063015f0cc13"
    static let minimalManifestCanonicalSHA =
        "aab326c8532a010dd46c769c043971a5ebb94dcbe8076cd923b02b1298dafe71"
}
