//! ADR 0041 acceptance tests for every `WitnessSignature` variant.
//!
//! Covers T-0041R.6: all three variants (Ed25519, EcdsaP256, SelfSigned),
//! the legacy bare-struct deserialization fallback, and JSON round-trip.
//!
//! These tests call `verify()` to exercise signature dispatch rather than
//! invoking the private `verify_witness_signature` directly. Serialization
//! tests operate on `WitnessSignature` values in isolation.

mod common;

use chrono::{Duration, Utc};
use cortex_verifier::{
    verify, AuthorityDomain, IndependentWitness, SelfSignedAlgorithm, SelfSignedKeyRegistry,
    VerifiedTrustState, WitnessClass, WitnessSignature, WitnessTier, WITNESS_SIGNATURE_INVALID,
};
use ed25519_dalek::{Signer, SigningKey};

use crate::common::{
    authority_grade_input, full_witness_set, sample_evidence_digest, sample_payload, signing_key,
    Kind,
};

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Build a signed `IndependentWitness` carrying a real Ed25519 signature
/// produced by `key`. The caller controls the payload so tests can corrupt
/// individual bytes after construction.
fn ed25519_witness(key: &SigningKey, tamper: bool) -> IndependentWitness {
    let digest = sample_evidence_digest();
    let now = Utc::now();
    let class = WitnessClass::SignedLedgerChainHead;
    let domain = class.required_authority_domain();
    let payload = sample_payload(class);
    let preimage = payload.canonical_preimage(domain, &digest, now);
    let sig = key.sign(&preimage);
    let mut sig_bytes = sig.to_bytes();
    if tamper {
        // Flip a single byte to produce an invalid signature.
        sig_bytes[0] ^= 0xFF;
    }
    IndependentWitness {
        class,
        authority_domain: domain,
        tier: WitnessTier::OperatorOwned,
        asserted_at: now,
        asserted_subject_blake3: digest,
        signature: WitnessSignature::Ed25519 {
            public_key_bytes: key.verifying_key().to_bytes(),
            signature_bytes: sig_bytes,
            signer_id: None,
        },
        payload,
    }
}

// ---------------------------------------------------------------------------
// Test 1: Ed25519 variant verifies a valid signature
// ---------------------------------------------------------------------------

#[test]
fn ed25519_variant_verifies_valid_signature() {
    let digest = sample_evidence_digest();
    let now = Utc::now();
    let max_age = Duration::seconds(86_400);

    // Replace one witness from the canonical full set with our locally
    // constructed Ed25519 witness so the whole set still reaches
    // FullChainVerified if the signature is valid.
    let key = signing_key(42);
    let good_witness = ed25519_witness(&key, /*tamper=*/ false);

    // Build the remaining three witnesses from the standard helper (different
    // seeds to keep authority domains disjoint), then swap in our witness.
    let mut witnesses = full_witness_set(&digest, now);
    // Replace the SignedLedgerChainHead witness (index 0) with ours.
    witnesses[0] = good_witness;
    // Ensure asserted_subject_blake3 aligns — the helper already uses
    // `sample_evidence_digest()` so this is consistent by construction.

    let state = verify(
        &authority_grade_input(Kind::ReleaseReadiness),
        &witnesses,
        now,
        max_age,
    );

    assert!(
        matches!(state, VerifiedTrustState::FullChainVerified { .. }),
        "expected FullChainVerified for a valid Ed25519 signature, got {state:?}",
    );
}

// ---------------------------------------------------------------------------
// Test 2: Ed25519 variant rejects a tampered signature
// ---------------------------------------------------------------------------

#[test]
fn ed25519_variant_rejects_tampered_signature() {
    let digest = sample_evidence_digest();
    let now = Utc::now();
    let max_age = Duration::seconds(86_400);

    let key = signing_key(43);
    let bad_witness = ed25519_witness(&key, /*tamper=*/ true);

    let mut witnesses = full_witness_set(&digest, now);
    witnesses[0] = bad_witness;

    let state = verify(
        &authority_grade_input(Kind::ReleaseReadiness),
        &witnesses,
        now,
        max_age,
    );

    match state {
        VerifiedTrustState::Broken { edge, .. } => {
            assert_eq!(
                edge.invariant, WITNESS_SIGNATURE_INVALID,
                "expected signature_invalid invariant, got {}",
                edge.invariant
            );
            assert!(
                edge.detail.contains("Ed25519"),
                "detail should name the algorithm: {}",
                edge.detail
            );
        }
        other => panic!("expected Broken(signature_invalid) for tampered signature, got {other:?}"),
    }
}

// ---------------------------------------------------------------------------
// Test 3: EcdsaP256 returns UnsupportedAlgorithm or verifies (adaptive)
// ---------------------------------------------------------------------------

#[test]
fn ecdsa_p256_returns_unsupported_or_verifies() {
    let digest = sample_evidence_digest();
    let now = Utc::now();
    let class = WitnessClass::SignedLedgerChainHead;
    let domain = AuthorityDomain::LocalSignedLedger;
    let payload = sample_payload(class);

    let witness = IndependentWitness {
        class,
        authority_domain: domain,
        tier: WitnessTier::OperatorOwned,
        asserted_at: now,
        asserted_subject_blake3: digest.clone(),
        signature: WitnessSignature::EcdsaP256 {
            // Placeholder DER bytes; verification is not yet implemented at
            // the verifier layer so actual key/sig content doesn't matter
            // for the UnsupportedAlgorithm path.
            public_key_der: vec![0x30, 0x00],
            signature_der: vec![0x30, 0x00],
        },
        payload,
    };

    let mut witnesses = full_witness_set(&digest, now);
    witnesses[0] = witness;

    let state = verify(
        &authority_grade_input(Kind::ReleaseReadiness),
        &witnesses,
        now,
        Duration::seconds(86_400),
    );

    match state {
        // T-0041R.5 done: full ECDSA P-256 verification implemented.
        VerifiedTrustState::FullChainVerified { .. } => {
            // Acceptable when p256 verification is wired in.
        }
        // Current state: adapter-boundary verification only; verifier returns
        // UnsupportedAlgorithm so the chain is Broken(signature_invalid).
        VerifiedTrustState::Broken { edge, .. } => {
            assert_eq!(
                edge.invariant, WITNESS_SIGNATURE_INVALID,
                "EcdsaP256 arm must fail with signature_invalid, got invariant={}",
                edge.invariant
            );
            assert!(
                edge.detail.contains("UnsupportedAlgorithm")
                    || edge.detail.contains("EcdsaP256")
                    || edge.detail.contains("not yet implemented"),
                "detail should name the unsupported variant: {}",
                edge.detail
            );
        }
        other => panic!(
            "expected FullChainVerified (if implemented) or Broken(signature_invalid), got {other:?}"
        ),
    }
}

// ---------------------------------------------------------------------------
// Test 4: SelfSigned with unknown key_id fails closed
// ---------------------------------------------------------------------------

#[test]
fn self_signed_returns_unsupported_for_unknown_key() {
    let digest = sample_evidence_digest();
    let now = Utc::now();
    let class = WitnessClass::SignedLedgerChainHead;
    let domain = AuthorityDomain::LocalSignedLedger;
    let payload = sample_payload(class);

    let witness = IndependentWitness {
        class,
        authority_domain: domain,
        tier: WitnessTier::OperatorOwned,
        asserted_at: now,
        asserted_subject_blake3: digest.clone(),
        signature: WitnessSignature::SelfSigned {
            key_id: "unknown-key".to_string(),
            signature_bytes: vec![],
        },
        payload,
    };

    let mut witnesses = full_witness_set(&digest, now);
    witnesses[0] = witness;

    let state = verify(
        &authority_grade_input(Kind::ReleaseReadiness),
        &witnesses,
        now,
        Duration::seconds(86_400),
    );

    match state {
        VerifiedTrustState::Broken { edge, .. } => {
            assert_eq!(
                edge.invariant, WITNESS_SIGNATURE_INVALID,
                "SelfSigned with unknown key_id must fail with signature_invalid, got invariant={}",
                edge.invariant
            );
            // The detail must identify the key_id so operators can diagnose
            // the missing registry entry.
            assert!(
                edge.detail.contains("unknown-key")
                    || edge.detail.contains("SelfSigned")
                    || edge.detail.contains("not in SelfSignedKeyRegistry")
                    || edge.detail.contains("not yet implemented"),
                "detail must reference the unknown key_id or registry: {}",
                edge.detail
            );
        }
        other => panic!(
            "expected Broken(signature_invalid) for unknown SelfSigned key_id, got {other:?}"
        ),
    }
}

// ---------------------------------------------------------------------------
// Test 5: Legacy flat JSON deserializes as Ed25519 variant
// ---------------------------------------------------------------------------

#[test]
fn legacy_flat_json_deserializes_as_ed25519_variant() {
    // This is the pre-enum bare-struct shape written by the ledger before the
    // ADR 0041 migration. The custom Deserialize fallback must accept it.
    let key = signing_key(7);
    let pk_hex = hex_encode(key.verifying_key().as_bytes());
    let sig_bytes = [0xABu8; 64];
    let sig_hex = hex_encode(&sig_bytes);

    let json =
        format!(r#"{{"verifying_key":"{pk_hex}","signature":"{sig_hex}","signer_id":null}}"#);

    let deserialized: WitnessSignature =
        serde_json::from_str(&json).expect("legacy JSON must deserialize without error");

    match deserialized {
        WitnessSignature::Ed25519 {
            public_key_bytes,
            signature_bytes,
            signer_id,
        } => {
            assert_eq!(
                &public_key_bytes,
                key.verifying_key().as_bytes(),
                "public_key_bytes must match the hex-decoded verifying_key field"
            );
            assert_eq!(
                signature_bytes, sig_bytes,
                "signature_bytes must match the hex-decoded signature field"
            );
            assert_eq!(signer_id, None, "signer_id: null must deserialize as None");
        }
        other => panic!("legacy JSON must deserialize as Ed25519 variant, got {other:?}"),
    }
}

// ---------------------------------------------------------------------------
// Test 6: Tagged Ed25519 JSON round-trips
// ---------------------------------------------------------------------------

#[test]
fn tagged_ed25519_json_round_trips() {
    let key = signing_key(8);
    let sig_bytes = [0x11u8; 64];

    let original = WitnessSignature::Ed25519 {
        public_key_bytes: key.verifying_key().to_bytes(),
        signature_bytes: sig_bytes,
        signer_id: Some("https://token.actions.githubusercontent.com".to_string()),
    };

    let json = serde_json::to_string(&original).expect("serialization must not fail");

    // The tagged serialization must emit the discriminant field.
    assert!(
        json.contains(r#""type":"ed25519""#),
        "serialized JSON must contain the type tag \"type\":\"ed25519\", got: {json}"
    );

    let roundtripped: WitnessSignature =
        serde_json::from_str(&json).expect("deserialization of tagged JSON must not fail");

    assert_eq!(
        original, roundtripped,
        "round-tripped WitnessSignature must equal the original"
    );
}

// ---------------------------------------------------------------------------
// Hex helper (avoids pulling a new dep into the test binary)
// ---------------------------------------------------------------------------

fn hex_encode(bytes: &[u8]) -> String {
    bytes
        .iter()
        .fold(String::with_capacity(bytes.len() * 2), |mut acc, b| {
            acc.push(char::from_digit(u32::from(b >> 4), 16).unwrap());
            acc.push(char::from_digit(u32::from(b & 0x0F), 16).unwrap());
            acc
        })
}

// ---------------------------------------------------------------------------
// Test 7: SelfSignedKeyRegistry::load succeeds for a well-formed TOML file
// ---------------------------------------------------------------------------

#[test]
fn load_registry_from_toml_file_succeeds() {
    use std::io::Write as _;

    let key = signing_key(99);
    let key_bytes_hex = hex_encode(key.verifying_key().as_bytes());

    let mut tmp = tempfile::NamedTempFile::new().expect("tempfile must be created");
    writeln!(
        tmp,
        r#"
[[keys]]
key_id = "test-operator-key"
algorithm = "ed25519"
key_bytes_hex = "{key_bytes_hex}"
"#,
    )
    .expect("write to tempfile must succeed");

    let path = tmp.path();
    let registry = SelfSignedKeyRegistry::load(path)
        .unwrap_or_else(|e| panic!("SelfSignedKeyRegistry::load must succeed: {e}"));

    assert_eq!(registry.len(), 1, "registry must contain exactly one entry");

    let entry = registry
        .get("test-operator-key")
        .expect("entry 'test-operator-key' must be present in registry");
    assert_eq!(
        entry.key_id, "test-operator-key",
        "key_id must round-trip from TOML"
    );
    assert_eq!(
        entry.algorithm,
        SelfSignedAlgorithm::Ed25519,
        "algorithm must deserialize as Ed25519"
    );
    assert_eq!(
        entry.key_bytes_hex, key_bytes_hex,
        "key_bytes_hex must round-trip from TOML"
    );
}

// ---------------------------------------------------------------------------
// Test 8: SelfSignedKeyRegistry::load returns Err for a missing file
// ---------------------------------------------------------------------------

#[test]
fn load_registry_missing_file_returns_error() {
    let result =
        SelfSignedKeyRegistry::load(std::path::Path::new("/nonexistent/path/witness-keys.toml"));
    assert!(
        result.is_err(),
        "load must return Err for a path that does not exist"
    );
    let msg = result.unwrap_err();
    assert!(
        msg.contains("witness-key-registry") || msg.contains("cannot read"),
        "error message must identify the problem: {msg}"
    );
}
