import Foundation
import Testing

@testable import NucleicCore

/// CARBON_SHARDING §16 "Crypto": determinism, tamper detection, frozen cross-platform
/// vectors. The frozen constants below were captured on macOS and must decode identically on
/// Linux (swift-crypto) — byte-stability across platforms is the acceptance bar for phase 1.
@Suite("Carbon crypto")
struct CarbonCryptoTests {
    /// A fixed 32-byte master key: 00 01 02 … 1f.
    private func fixedKey() -> CarbonCrypto.ProjectKey {
        try! CarbonCrypto.ProjectKey(bytes: Data((0..<32).map { UInt8($0) }))
    }

    private func hex(_ data: Data) -> String {
        data.map { String(format: "%02x", $0) }.joined()
    }

    @Test func keyLengthIsEnforced() {
        #expect(throws: CarbonCryptoError.invalidKeyLength(16)) {
            _ = try CarbonCrypto.ProjectKey(bytes: Data(count: 16))
        }
        #expect((try? CarbonCrypto.ProjectKey(bytes: Data(count: 32))) != nil)
    }

    @Test func sealingIsDeterministic() throws {
        let crypto = CarbonCrypto(projectKey: fixedKey())
        let plaintext = Data("the same plaintext, sealed twice".utf8)
        let a = try crypto.seal(plaintext)
        let b = try crypto.seal(plaintext)
        #expect(a.ciphertext == b.ciphertext)
        #expect(a.id == b.id)
        #expect(a.id == CarbonCrypto.blobID(a.ciphertext))
    }

    @Test func roundTrip() throws {
        let crypto = CarbonCrypto(projectKey: fixedKey())
        let plaintext = Data((0..<4096).map { UInt8($0 % 251) })
        let sealed = try crypto.seal(plaintext)
        #expect(try crypto.open(sealed.ciphertext) == plaintext)
    }

    @Test func distinctPlaintextsProduceDistinctBlobs() throws {
        let crypto = CarbonCrypto(projectKey: fixedKey())
        let a = try crypto.seal(Data("shard A".utf8))
        let b = try crypto.seal(Data("shard B".utf8))
        #expect(a.id != b.id)
        // Derived nonces differ too — nonce reuse across distinct plaintexts is impossible.
        #expect(a.ciphertext.prefix(12) != b.ciphertext.prefix(12))
    }

    @Test func distinctKeysProduceDistinctBlobs() throws {
        let plaintext = Data("same plaintext, different projects".utf8)
        let a = try CarbonCrypto(projectKey: fixedKey()).seal(plaintext)
        let otherKey = try CarbonCrypto.ProjectKey(bytes: Data((1...32).map { UInt8($0) }))
        let b = try CarbonCrypto(projectKey: otherKey).seal(plaintext)
        #expect(a.id != b.id)
    }

    @Test func anyFlippedBitFailsOpen() throws {
        let crypto = CarbonCrypto(projectKey: fixedKey())
        let sealed = try crypto.seal(Data("tamper with me".utf8))
        // One flip in each region: nonce, ciphertext body, GCM tag.
        for index in [0, 12, sealed.ciphertext.count - 1] {
            var tampered = sealed.ciphertext
            tampered[index] ^= 0x01
            #expect(throws: (any Error).self) { _ = try crypto.open(tampered) }
        }
    }

    @Test func malformedBlobIsRejected() {
        let crypto = CarbonCrypto(projectKey: fixedKey())
        #expect(throws: CarbonCryptoError.malformedBlob) {
            _ = try crypto.open(Data(count: 27))  // below nonce+tag minimum
        }
    }

    @Test func blobIDIsLowercaseSHA256Hex() {
        // Known SHA-256("abc") — proves the ID is plain SHA-256 of the bytes, hex-lowercase.
        #expect(
            CarbonCrypto.blobID(Data("abc".utf8))
                == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
    }

    @Test func openRejectsCrossKeyBlob() throws {
        let sealed = try CarbonCrypto(projectKey: fixedKey()).seal(Data("secret".utf8))
        let otherKey = try CarbonCrypto.ProjectKey(bytes: Data(repeating: 0xAA, count: 32))
        #expect(throws: CarbonCryptoError.integrityFailure) {
            _ = try CarbonCrypto(projectKey: otherKey).open(sealed.ciphertext)
        }
    }

    /// The frozen cross-platform vector (spec §16): fixed key + fixed plaintext ⇒ exact blob
    /// bytes and ID, identical on Darwin (CryptoKit) and Linux (swift-crypto). If this test
    /// ever needs a new expectation, that is a **wire-breaking event**, not a test update.
    @Test func frozenVector() throws {
        let crypto = CarbonCrypto(projectKey: fixedKey())
        let sealed = try crypto.seal(Data("nucleic carbon vector 1".utf8))
        #expect(hex(sealed.ciphertext) == CarbonFrozenVectors.vector1BlobHex)
        #expect(sealed.id == CarbonFrozenVectors.vector1ID)
    }

    @Test func frozenEmptyPlaintextVector() throws {
        let sealed = try CarbonCrypto(projectKey: fixedKey()).seal(Data())
        #expect(hex(sealed.ciphertext) == CarbonFrozenVectors.emptyBlobHex)
        #expect(sealed.id == CarbonFrozenVectors.emptyID)
    }
}

/// Captured once (macOS, swift-crypto-compatible construction) and frozen. Linux must match.
enum CarbonFrozenVectors {
    static let vector1BlobHex =
        "e9387e1492cbafdbf992f20ba8d702e1c6b0cea4ad6911289cb31ea80c7f381e3616c079a15e26a83604c43e324ec3e11299e0"
    static let vector1ID = "3c292ad63f604497ae8bf9a13907053c524886b59f4dc0cd6a8368d2be7b3ec5"
    static let emptyBlobHex = "c45e7d3d189d01c8ccdb8d0b0b1b158af00269400dd6594dea9d8b5f"
    static let emptyID = "ca35ccd5d12cde196d25db0bf5a4de3fae8e570a033673970340d3cb5ff55703"
}
