//! Cryptographic signature verification stage for the labeler conformance suite.
//!
//! This stage verifies that labels published by a labeler are correctly signed
//! using the labeler's declared signing key. It implements DRISL-CBOR canonicalization
//! (deterministic canonical encoding per RFC 8949) and supports key rotation via
//! the did:plc audit log.

use std::borrow::Cow;
use std::collections::BTreeMap;

use atrium_api::com::atproto::label::defs::Label;
use ciborium::value::Value;
use sha2::{Digest, Sha256};
use thiserror::Error;

use crate::commands::test::labeler::report::{CheckResult, CheckStatus, Stage};

/// Checks emitted by the crypto stage.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Check {
    /// Overall rollup result for the crypto stage.
    Rollup,
    /// A label could not be canonicalized for signing.
    CanonicalizationFailed,
    /// PLC audit log fetch for historic key fallback.
    PlcHistoryFetch,
    /// Labels were verified only against a rotated-out key.
    RotatedKeysUsed,
    /// A label failed signature verification.
    LabelVerificationFailed,
    /// The signature bytes on a label could not be parsed.
    SignatureBytesUnparseable,
}

impl Check {
    /// Stable check ID string used in `CheckResult.id`.
    pub fn id(self) -> &'static str {
        match self {
            Check::Rollup => "crypto::rollup",
            Check::CanonicalizationFailed => "crypto::canonicalization_failed",
            Check::PlcHistoryFetch => "crypto::plc_history_fetch",
            Check::RotatedKeysUsed => "crypto::rotated_keys_used",
            Check::LabelVerificationFailed => "crypto::label_verification_failed",
            Check::SignatureBytesUnparseable => "crypto::signature_bytes_unparseable",
        }
    }

    pub fn pass(self) -> CheckResult {
        CheckResult {
            id: self.id(),
            stage: Stage::Crypto,
            status: CheckStatus::Pass,
            summary: Cow::Borrowed(match self {
                Check::Rollup => "All labels verified with current or historic keys",
                _ => "crypto check passed",
            }),
            diagnostic: None,
            skipped_reason: None,
        }
    }

    pub fn spec_violation(
        self,
        diagnostic: Box<dyn miette::Diagnostic + Send + Sync>,
    ) -> CheckResult {
        CheckResult {
            id: self.id(),
            stage: Stage::Crypto,
            status: CheckStatus::SpecViolation,
            summary: Cow::Borrowed(match self {
                Check::Rollup => "Labels failed verification",
                Check::CanonicalizationFailed => "Label canonicalization failed",
                Check::LabelVerificationFailed => "Label signature verification failed",
                Check::SignatureBytesUnparseable => "Signature bytes are unparseable",
                _ => "crypto check failed",
            }),
            diagnostic: Some(diagnostic),
            skipped_reason: None,
        }
    }

    pub fn network_error(
        self,
        diagnostic: Box<dyn miette::Diagnostic + Send + Sync>,
    ) -> CheckResult {
        CheckResult {
            id: self.id(),
            stage: Stage::Crypto,
            status: CheckStatus::NetworkError,
            summary: Cow::Borrowed(match self {
                Check::PlcHistoryFetch => "PLC history fetch failed",
                _ => "crypto network error",
            }),
            diagnostic: Some(diagnostic),
            skipped_reason: None,
        }
    }

    pub fn advisory(self) -> CheckResult {
        CheckResult {
            id: self.id(),
            stage: Stage::Crypto,
            status: CheckStatus::Advisory,
            summary: Cow::Borrowed(match self {
                Check::RotatedKeysUsed => "Labels signed by rotated-out key",
                _ => "crypto advisory",
            }),
            diagnostic: None,
            skipped_reason: None,
        }
    }

    pub fn skip(self, reason: impl Into<Cow<'static, str>>) -> CheckResult {
        CheckResult {
            id: self.id(),
            stage: Stage::Crypto,
            status: CheckStatus::Skipped,
            summary: Cow::Borrowed(match self {
                Check::Rollup => "Crypto stage (no labels to verify)",
                _ => "crypto check skipped",
            }),
            diagnostic: None,
            skipped_reason: Some(reason.into()),
        }
    }
}

/// The canonical form of a label as it was signed by the labeler.
pub struct CanonicalLabel {
    /// SHA-256 hash of the canonical bytes (with sig stripped).
    pub prehash: [u8; 32],
    /// The DRISL-CBOR bytes that were hashed (sig stripped).
    pub canonical_bytes: Vec<u8>,
    /// Raw signature bytes (r || s) extracted from the sig field.
    pub signature_bytes: Vec<u8>,
}

/// Error from label canonicalization.
#[derive(Debug, Clone, Error)]
pub enum CanonicalizeError {
    /// The serialized CBOR representation could not be produced.
    #[error("Invalid label CBOR: {cause}")]
    InvalidLabelCbor {
        /// Details of the serialization failure.
        cause: String,
    },
    /// The label contains a floating-point value (not allowed in DRISL).
    #[error("Floating-point values are not allowed in labels")]
    FloatRejected,
    /// The label contains indefinite-length CBOR items (not allowed in DRISL).
    #[error("Indefinite-length items are not allowed in labels")]
    IndefiniteLengthRejected,
    /// The label is missing a `sig` field.
    #[error("Label is missing a 'sig' field")]
    MissingSigField,
    /// The `sig` field is not a byte string.
    #[error("The 'sig' field must be a CBOR byte string")]
    SigFieldWrongType,
    /// The `sig` field does not contain exactly 64 bytes (r || s).
    #[error("The 'sig' field must be 64 bytes (r || s concatenated), got {actual}")]
    SigFieldWrongLength {
        /// The actual length of the signature field.
        actual: usize,
    },
}

/// Error from parsing a signature from raw bytes.
#[derive(Debug, Clone, Error)]
pub enum SignatureParseError {
    /// The signature bytes could not be parsed as a secp256k1 (k256) signature.
    #[error("Failed to parse signature as secp256k1: {cause}")]
    K256Failed {
        /// Details of the parsing failure.
        cause: String,
    },
    /// The signature bytes could not be parsed as a NIST P-256 (p256) signature.
    #[error("Failed to parse signature as NIST P-256: {cause}")]
    P256Failed {
        /// Details of the parsing failure.
        cause: String,
    },
}

/// Error from the crypto stage covering signature verification failures.
#[derive(Debug, Clone, Error, miette::Diagnostic)]
pub enum CryptoCheckError {
    /// Current key verification failed; no rotation history available.
    #[error(
        "labels failed verification against current key \"{current_key_id}\" and did:web provides no rotation history"
    )]
    #[diagnostic(code = "labeler::crypto::did_web_no_rotation_history")]
    DidWebNoRotationHistory {
        /// The current key id that failed verification.
        current_key_id: String,
    },
    /// Neither current nor historic keys could verify the labels.
    #[error(
        "some labels could not be verified against any of the {} tried key id(s): {tried_keys:?}",
        tried_keys.len()
    )]
    #[diagnostic(code = "labeler::crypto::multi_key_verification_failed")]
    MultiKeyVerificationFailed {
        /// List of all key ids that were tried.
        tried_keys: Vec<String>,
    },
    /// Network error fetching PLC audit log prevented checking historic keys.
    #[error("failed to fetch PLC audit log for {did}: {reason}")]
    #[diagnostic(code = "labeler::crypto::plc_history_fetch_network_error")]
    PlcHistoryFetchNetworkError {
        /// The labeler's DID.
        did: String,
        /// Reason for the network failure.
        reason: String,
    },
    /// Label canonicalization failed.
    #[error("failed to canonicalize label {label_uri} for signing")]
    #[diagnostic(code = "labeler::crypto::label_canonicalization_failed")]
    LabelCanonicalizationFailed {
        /// The label's URI for context.
        label_uri: String,
        /// The underlying canonicalization error.
        #[source]
        source: CanonicalizeError,
    },
    /// The signature bytes on a label could not be parsed for the current key's curve.
    #[error(
        "signature field for label {label_uri} is not a valid {curve} ECDSA signature for the current key"
    )]
    #[diagnostic(code = "labeler::crypto::signature_bytes_unparseable")]
    SignatureBytesUnparseable {
        /// The label's URI for context.
        label_uri: String,
        /// The current key's curve name.
        curve: &'static str,
    },
    /// A label failed verification against the current key and PLC history could not be consulted.
    #[error(
        "label {label_uri} failed verification against current key \"{current_key_id}\" and PLC history could not be consulted"
    )]
    #[diagnostic(code = "labeler::crypto::label_verification_failed_no_history")]
    LabelVerificationFailedNoHistory {
        /// The current key id that failed verification.
        current_key_id: String,
        /// The label's URI for context.
        label_uri: String,
    },
}

/// Canonicalize a label for signature verification.
///
/// This function:
/// 1. Serializes the label to a `ciborium::Value` tree.
/// 2. Validates the tree (rejects floats and indefinite-length items).
/// 3. Extracts and validates the `sig` field (must be 64-byte byte string).
/// 4. Removes the `sig` field from the tree.
/// 5. Sorts all map keys by their canonical CBOR-encoded byte representation (RFC 8949 deterministic encoding).
/// 6. Re-serializes the sorted tree to deterministic CBOR bytes.
/// 7. Computes the SHA-256 prehash of the canonical bytes.
///
/// Returns a `CanonicalLabel` containing the prehash, canonical bytes, and signature.
pub fn canonicalize_label_for_signing(label: &Label) -> Result<CanonicalLabel, CanonicalizeError> {
    // Canonicalize `label.data` (the plain `LabelData`) rather than the
    // `Object<LabelData>` wrapper: atrium preserves unknown JSON fields on
    // the wrapper's `extra_data` bag and re-serializes them as siblings,
    // which leaks server-side metadata (e.g. database ids) into the form
    // that gets signed. Labelers sign only the spec's own fields, so
    // including `extra_data` in the prehash causes verification to fail
    // against any labeler whose REST response carries extra JSON fields.
    let mut value: Value = ciborium::value::Value::serialized(&label.data).map_err(|e| {
        CanonicalizeError::InvalidLabelCbor {
            cause: format!("{e}"),
        }
    })?;

    // floats should never appear, but we check defensively.
    validate_value(&value)?;

    let signature_bytes = extract_and_remove_sig(&mut value)?;

    canonicalize_tree(&mut value)?;

    let mut canonical_bytes = Vec::new();
    ciborium::ser::into_writer(&value, &mut canonical_bytes).map_err(|e| {
        CanonicalizeError::InvalidLabelCbor {
            cause: format!("Re-serialization failed: {e}"),
        }
    })?;

    let prehash: [u8; 32] = Sha256::digest(&canonical_bytes).into();

    Ok(CanonicalLabel {
        prehash,
        canonical_bytes,
        signature_bytes,
    })
}

/// Validate that the value tree contains no floats or indefinite-length items.
fn validate_value(value: &Value) -> Result<(), CanonicalizeError> {
    match value {
        Value::Null | Value::Bool(_) | Value::Integer(_) | Value::Bytes(_) | Value::Text(_) => {
            Ok(())
        }
        Value::Float(_) => Err(CanonicalizeError::FloatRejected),
        Value::Array(arr) => {
            for item in arr {
                validate_value(item)?;
            }
            Ok(())
        }
        Value::Map(map) => {
            for (k, v) in map {
                validate_value(k)?;
                validate_value(v)?;
            }
            Ok(())
        }
        Value::Tag(_, val) => validate_value(val),
        _ => Ok(()),
    }
}

/// Extract the `sig` field from the top-level map and remove it.
///
/// The `sig` field must be:
/// - A byte string (not any other type).
/// - Exactly 64 bytes long (r || s concatenated).
fn extract_and_remove_sig(value: &mut Value) -> Result<Vec<u8>, CanonicalizeError> {
    match value {
        Value::Map(map) => {
            // Find and extract the "sig" entry.
            let sig_key = Value::Text("sig".to_string());
            let mut sig_value = None;

            // Find the position of the "sig" key.
            let sig_index = map.iter().position(|(k, _)| k == &sig_key);

            // Extract and remove the sig entry.
            if let Some(idx) = sig_index {
                let (_, val) = map.remove(idx);
                sig_value = Some(val);
            }

            let sig_value = sig_value.ok_or(CanonicalizeError::MissingSigField)?;

            // Validate and extract the signature bytes.
            match sig_value {
                Value::Bytes(ref bytes) => {
                    if bytes.len() != 64 {
                        return Err(CanonicalizeError::SigFieldWrongLength {
                            actual: bytes.len(),
                        });
                    }
                    Ok(bytes.clone())
                }
                _ => Err(CanonicalizeError::SigFieldWrongType),
            }
        }
        _ => Err(CanonicalizeError::MissingSigField),
    }
}

/// Canonicalize a value tree in-place by sorting maps by their canonical CBOR-encoded key bytes.
///
/// Per RFC 8949 deterministic encoding, map keys must be sorted lexicographically
/// by their canonical CBOR byte representation, not by raw string value.
fn canonicalize_tree(value: &mut Value) -> Result<(), CanonicalizeError> {
    match value {
        Value::Array(arr) => {
            for item in arr {
                canonicalize_tree(item)?;
            }
            Ok(())
        }
        Value::Map(map) => {
            // Recursively canonicalize all values.
            for (_, v) in map.iter_mut() {
                canonicalize_tree(v)?;
            }

            // Sort map entries by their canonical CBOR-encoded key bytes.
            let mut entries: Vec<_> = std::mem::take(map);
            entries.sort_by(|(k1, _), (k2, _)| {
                let bytes1 = encode_key_to_bytes(k1);
                let bytes2 = encode_key_to_bytes(k2);
                bytes1.cmp(&bytes2)
            });

            // Rebuild the map with sorted entries.
            *map = entries;

            Ok(())
        }
        Value::Tag(_, val) => canonicalize_tree(val),
        _ => Ok(()),
    }
}

/// Encode a CBOR key value to its canonical byte representation.
///
/// This is used for sorting keys in deterministic order.
fn encode_key_to_bytes(value: &Value) -> Vec<u8> {
    let mut bytes = Vec::new();
    let _ = ciborium::ser::into_writer(value, &mut bytes);
    bytes
}

/// Facts gathered from the crypto stage, populated only when checks pass.
#[derive(Debug, Clone)]
pub struct CryptoFacts {
    /// Number of labels verified with the current (declared) key.
    pub verified_with_current: usize,
    /// Hits from historic key verification (key ID -> label count).
    pub verified_with_historic: Vec<HistoricKeyHit>,
    /// Number of labels that could not be verified.
    pub unverified: usize,
}

/// Record of a successful verification with a historic (rotated-out) key.
#[derive(Debug, Clone)]
pub struct HistoricKeyHit {
    /// The multikey string of the key that verified these labels.
    pub key_id: String,
    /// Count of labels verified with this key.
    pub label_count: usize,
}

/// Output from the crypto stage.
#[derive(Debug)]
pub struct CryptoStageOutput {
    /// Facts populated only when all checks pass.
    pub facts: Option<CryptoFacts>,
    /// All check results from this stage.
    pub results: Vec<CheckResult>,
}

/// A label that failed to verify against the current key.
#[derive(Debug, Clone)]
struct FailedLabel {
    /// The label that failed.
    label: Label,
    /// Canonicalization error, if any (else signature mismatch).
    canonicalization_error: Option<CanonicalizeError>,
}

/// Run the crypto stage: verify labels against identity's signing key, with PLC history fallback.
///
/// Logic:
/// 1. Empty labels → emit Skipped.
/// 2. For each label: canonicalize → on error emit per-label SpecViolation.
/// 3. Verify against `identity.signing_key` → on success increment `verified_with_current`.
/// 4. On all-pass: emit `crypto::rollup` Pass.
/// 5. Else if `did:plc`: fetch PLC audit log and retry against historic keys.
/// 6. Else (`did:web`): emit `crypto::rollup` SpecViolation with no rotation history.
pub async fn run(
    identity: &crate::commands::test::labeler::identity::IdentityFacts,
    labels: &[Label],
    http: &dyn crate::common::identity::HttpClient,
) -> CryptoStageOutput {
    // Handle the empty-labels case early.
    if labels.is_empty() {
        return CryptoStageOutput {
            facts: None,
            results: vec![Check::Rollup.skip("labeler published no labels; nothing to verify")],
        };
    }

    let mut results = Vec::new();
    let mut verified_with_current = 0usize;
    let mut failed_against_current: Vec<FailedLabel> = Vec::new();

    // Verify all labels against the current key.
    for label in labels {
        match canonicalize_label_for_signing(label) {
            Err(err) => {
                // Canonicalization failed — emit a per-label SpecViolation.
                let diagnostic = CryptoCheckError::LabelCanonicalizationFailed {
                    label_uri: label.uri.clone(),
                    source: err.clone(),
                };
                results.push(Check::CanonicalizationFailed.spec_violation(Box::new(diagnostic)));
                failed_against_current.push(FailedLabel {
                    label: label.clone(),
                    canonicalization_error: Some(err),
                });
            }
            Ok(canonical) => {
                // Try to parse the signature.
                match parse_signature(&canonical.signature_bytes, &identity.signing_key) {
                    Err(_) => {
                        // Signature parsing failed.
                        let diagnostic = CryptoCheckError::SignatureBytesUnparseable {
                            label_uri: label.uri.clone(),
                            curve: identity.signing_key.curve_name(),
                        };
                        results.push(
                            Check::SignatureBytesUnparseable.spec_violation(Box::new(diagnostic)),
                        );
                        failed_against_current.push(FailedLabel {
                            label: label.clone(),
                            canonicalization_error: None,
                        });
                    }
                    Ok(signature) => {
                        // Try to verify against the current key.
                        match identity
                            .signing_key
                            .verify_prehash(&canonical.prehash, &signature)
                        {
                            Ok(()) => {
                                verified_with_current += 1;
                            }
                            Err(_) => {
                                failed_against_current.push(FailedLabel {
                                    label: label.clone(),
                                    canonicalization_error: None,
                                });
                            }
                        }
                    }
                }
            }
        }
    }

    tracing::debug!(
        total_labels = labels.len(),
        verified_with_current,
        failed = failed_against_current.len(),
        "crypto stage: current-key verification complete"
    );

    // Check if all labels verified with current key.
    if failed_against_current.is_empty() {
        results.push(CheckResult {
            summary: Cow::Owned(format!(
                "{verified_with_current} labels verified against current key"
            )),
            ..Check::Rollup.pass()
        });
        return CryptoStageOutput {
            facts: Some(CryptoFacts {
                verified_with_current,
                verified_with_historic: Vec::new(),
                unverified: 0,
            }),
            results,
        };
    }

    // Some labels failed current-key verification; check DID type for history fallback.
    match identity.did.method() {
        crate::common::identity::DidMethod::Plc => {
            tracing::debug!(
                did = %identity.did,
                "crypto stage: fetching PLC audit log for historic keys"
            );
            match crate::common::identity::plc_history_for_fragment(
                &identity.did,
                "atproto_label",
                http,
            )
            .await
            {
                Err(e) => {
                    // Transport error fetching PLC history.
                    let diagnostic = CryptoCheckError::PlcHistoryFetchNetworkError {
                        did: identity.did.to_string(),
                        reason: format!("{e}"),
                    };
                    results.push(Check::PlcHistoryFetch.network_error(Box::new(diagnostic)));

                    // Emit Fail for each failed label since history could not be consulted.
                    for failed in &failed_against_current {
                        let diagnostic = CryptoCheckError::LabelVerificationFailedNoHistory {
                            current_key_id: identity.signing_key_id.clone(),
                            label_uri: failed.label.uri.clone(),
                        };
                        results.push(
                            Check::LabelVerificationFailed.spec_violation(Box::new(diagnostic)),
                        );
                    }
                    CryptoStageOutput {
                        facts: None,
                        results,
                    }
                }
                Ok(historic_keys) => {
                    tracing::debug!(
                        historic_key_count = historic_keys.len(),
                        "crypto stage: PLC audit log returned historic keys"
                    );
                    let mut historic_hits: BTreeMap<String, usize> = BTreeMap::new();
                    let mut tried_historic_key_ids = Vec::new();

                    // Try each historic key against remaining failed labels.
                    for historic_key in historic_keys {
                        tracing::debug!(
                            key_id = %historic_key.key_id,
                            "crypto stage: attempting verification with historic key"
                        );
                        if failed_against_current.is_empty() {
                            break; // All labels found matches.
                        }

                        // Parse the multikey.
                        match crate::common::identity::parse_multikey(&historic_key.key_id) {
                            Err(_) => {
                                // Skip parse failures; log and continue.
                                tracing::warn!(
                                    key_id = %historic_key.key_id,
                                    "failed to parse historic multikey"
                                );
                                tried_historic_key_ids.push(historic_key.key_id.clone());
                                continue;
                            }
                            Ok(parsed) => {
                                // Track that we attempted this historic key.
                                tried_historic_key_ids.push(historic_key.key_id.clone());
                                // Try to verify each failed label against this historic key.
                                let mut newly_verified = Vec::new();
                                for (i, failed) in failed_against_current.iter().enumerate() {
                                    // Skip if canonicalization failed (can't retry with different key).
                                    if failed.canonicalization_error.is_some() {
                                        continue;
                                    }

                                    // Re-canonicalize for verification.
                                    if let Ok(canonical) =
                                        canonicalize_label_for_signing(&failed.label)
                                    {
                                        // Parse signature for the historic key's curve.
                                        if let Ok(signature) = parse_signature(
                                            &canonical.signature_bytes,
                                            &parsed.verifying_key,
                                        ) {
                                            if parsed
                                                .verifying_key
                                                .verify_prehash(&canonical.prehash, &signature)
                                                .is_ok()
                                            {
                                                newly_verified.push(i);
                                                *historic_hits
                                                    .entry(historic_key.key_id.clone())
                                                    .or_insert(0) += 1;
                                            }
                                        }
                                    }
                                }

                                // Remove newly verified labels from the failed buffer (in reverse order).
                                for i in newly_verified.iter().rev() {
                                    failed_against_current.remove(*i);
                                }
                            }
                        }
                    }

                    // Determine final outcome.
                    if failed_against_current.is_empty() {
                        // All labels found historic-key matches.
                        let total_count: usize = historic_hits.values().sum();
                        let distinct_count = historic_hits.len();
                        results.push(CheckResult {
                            summary: Cow::Owned(format!(
                                "{total_count} label(s) signed by a rotated-out key ({distinct_count} distinct key id(s))"
                            )),
                            ..Check::RotatedKeysUsed.advisory()
                        });
                        results.push(Check::Rollup.pass());
                        CryptoStageOutput {
                            facts: Some(CryptoFacts {
                                verified_with_current,
                                verified_with_historic: historic_hits
                                    .into_iter()
                                    .map(|(key_id, label_count)| HistoricKeyHit {
                                        key_id,
                                        label_count,
                                    })
                                    .collect(),
                                unverified: 0,
                            }),
                            results,
                        }
                    } else {
                        // Some labels remain unverified after trying all keys.
                        // List all keys that were tried, including those that did not verify
                        // anything. Normalise every entry to a bare multibase-`z` multikey so
                        // the current key and historic keys render in the same shape, then
                        // drop duplicates (a historic entry may repeat the current key if it
                        // was never actually rotated).
                        let mut tried_keys = vec![identity.signing_key_multikey.clone()];
                        for raw in &tried_historic_key_ids {
                            let normalised =
                                raw.strip_prefix("did:key:").unwrap_or(raw).to_string();
                            if !tried_keys.contains(&normalised) {
                                tried_keys.push(normalised);
                            }
                        }
                        let diagnostic = CryptoCheckError::MultiKeyVerificationFailed {
                            tried_keys: tried_keys.clone(),
                        };
                        results.push(CheckResult {
                            summary: Cow::Owned(format!(
                                "Some labels could not be verified against any key (tried {} key id(s))",
                                tried_keys.len()
                            )),
                            ..Check::Rollup.spec_violation(Box::new(diagnostic))
                        });
                        CryptoStageOutput {
                            facts: None,
                            results,
                        }
                    }
                }
            }
        }
        _ => {
            // did:web or other method; no rotation history available.
            let diagnostic = CryptoCheckError::DidWebNoRotationHistory {
                current_key_id: identity.signing_key_id.clone(),
            };
            results.push(CheckResult {
                summary: Cow::Borrowed(
                    "Labels failed verification and did:web provides no rotation history",
                ),
                ..Check::Rollup.spec_violation(Box::new(diagnostic))
            });
            CryptoStageOutput {
                facts: None,
                results,
            }
        }
    }
}

/// Parse a signature from raw 64-byte (r || s) format into an AnySignature.
///
/// Takes the curve variant from the verifying key to determine which curve
/// to use for parsing. This ensures the signature is parsed for the correct curve,
/// not via trial-and-error.
fn parse_signature(
    bytes: &[u8],
    verifying_key: &crate::common::identity::AnyVerifyingKey,
) -> Result<crate::common::identity::AnySignature, SignatureParseError> {
    match verifying_key {
        crate::common::identity::AnyVerifyingKey::K256(_) => {
            k256::ecdsa::Signature::from_slice(bytes)
                .map(crate::common::identity::AnySignature::K256)
                .map_err(|e| SignatureParseError::K256Failed {
                    cause: format!("{e}"),
                })
        }
        crate::common::identity::AnyVerifyingKey::P256(_) => {
            p256::ecdsa::Signature::from_slice(bytes)
                .map(crate::common::identity::AnySignature::P256)
                .map_err(|e| SignatureParseError::P256Failed {
                    cause: format!("{e}"),
                })
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::common::identity::{AnySignature, AnyVerifyingKey};
    use atrium_api::com::atproto::label::defs::{Label, LabelData};
    use atrium_api::types::string::Datetime;
    use k256::ecdsa::SigningKey as K256SigningKey;
    use k256::ecdsa::signature::hazmat::PrehashSigner;

    /// Test that the canonicalizer correctly rejects floats.
    #[test]
    fn canonicalize_rejects_nan_float() {
        // Create a Value tree with a float directly (bypassing the Label schema).
        let value = Value::Map(vec![(
            Value::Text("test".to_string()),
            Value::Float(std::f64::consts::PI),
        )]);

        let result = validate_value(&value);
        assert!(matches!(result, Err(CanonicalizeError::FloatRejected)));
    }

    /// Test that missing sig field is rejected.
    #[test]
    fn canonicalize_missing_sig_errors() {
        // Create a simple label value without a sig field.
        let mut value = Value::Map(vec![(
            Value::Text("ver".to_string()),
            Value::Integer(1.into()),
        )]);

        let result = extract_and_remove_sig(&mut value);
        assert!(matches!(result, Err(CanonicalizeError::MissingSigField)));
    }

    /// Test that a fully-signed label round-trips through canonicalize → sign_prehash →
    /// verify_prehash using a real deterministic ECDSA keypair. This exercises the full
    /// cryptographic primitive path that the crypto stage relies on.
    #[test]
    fn sign_and_verify_label_roundtrip_k256() {
        // Deterministic seed so the test is bisect-stable.
        let seed: [u8; 32] = [7u8; 32];
        let signing_key = K256SigningKey::from_slice(&seed).expect("valid secret scalar");
        let verifying_key = AnyVerifyingKey::K256(*signing_key.verifying_key());

        // to obtain the prehash the labeler would sign over.
        let placeholder: Label = LabelData {
            cid: None,
            cts: Datetime::new("2026-01-01T00:00:00.000Z".parse().expect("valid datetime")),
            exp: None,
            neg: Some(false),
            sig: Some(vec![0u8; 64]),
            src: "did:plc:test123456789abcdefghijklmnop"
                .parse()
                .expect("valid did"),
            uri: "at://did:plc:test123456789abcdefghijklmnop/app.bsky.feed.post/abc1".to_string(),
            val: "spam".to_string(),
            ver: Some(1),
        }
        .into();
        let canonical =
            canonicalize_label_for_signing(&placeholder).expect("canonicalize placeholder label");

        let sig: k256::ecdsa::Signature = signing_key
            .sign_prehash(&canonical.prehash)
            .expect("sign prehash");
        let sig_bytes = sig.to_bytes().to_vec();
        assert_eq!(sig_bytes.len(), 64, "k256 signature must be 64 bytes");

        // The sig field is stripped before hashing so the prehash must be identical
        // to the prehash computed from the placeholder label.
        let mut signed_data = placeholder.data.clone();
        signed_data.sig = Some(sig_bytes.clone());
        let signed: Label = signed_data.into();
        let signed_canonical =
            canonicalize_label_for_signing(&signed).expect("canonicalize signed label");
        assert_eq!(
            signed_canonical.prehash, canonical.prehash,
            "prehash must be invariant over changes to the sig field"
        );
        assert_eq!(signed_canonical.signature_bytes, sig_bytes);

        // AnyVerifyingKey::verify_prehash path the crypto stage uses at runtime.
        let any_sig = AnySignature::K256(
            k256::ecdsa::Signature::from_slice(&signed_canonical.signature_bytes)
                .expect("parse signature"),
        );
        verifying_key
            .verify_prehash(&signed_canonical.prehash, &any_sig)
            .expect("signature must verify against the signing key");
    }

    /// Extra JSON fields (e.g. server-side database ids) that atrium preserves
    /// on `Label.extra_data` must NOT leak into the canonical prehash. Labelers
    /// sign only the spec's own fields, so any extra field leaking into the
    /// prehash would cause verification to fail against every label served by
    /// a labeler whose REST response carries metadata sibling keys.
    #[test]
    fn canonicalize_ignores_extra_data_fields() {
        let with_id: Label = serde_json::from_str(
            r#"{
                "id": 42,
                "src": "did:plc:test123456789abcdefghijklmnop",
                "uri": "at://did:plc:test123456789abcdefghijklmnop/app.bsky.feed.post/abc1",
                "val": "spam",
                "cts": "2026-01-01T00:00:00.000Z",
                "neg": false,
                "ver": 1,
                "sig": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            }"#,
        )
        .expect("parse label with extra field");

        let without_id: Label = serde_json::from_str(
            r#"{
                "src": "did:plc:test123456789abcdefghijklmnop",
                "uri": "at://did:plc:test123456789abcdefghijklmnop/app.bsky.feed.post/abc1",
                "val": "spam",
                "cts": "2026-01-01T00:00:00.000Z",
                "neg": false,
                "ver": 1,
                "sig": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            }"#,
        )
        .expect("parse label without extra field");

        let with_canonical =
            canonicalize_label_for_signing(&with_id).expect("canonicalize label with id");
        let without_canonical =
            canonicalize_label_for_signing(&without_id).expect("canonicalize label without id");

        assert_eq!(
            with_canonical.canonical_bytes, without_canonical.canonical_bytes,
            "extra JSON fields must not change the canonical bytes"
        );
        assert_eq!(
            with_canonical.prehash, without_canonical.prehash,
            "extra JSON fields must not change the prehash"
        );
    }

    /// Test that sig field with wrong length is rejected.
    #[test]
    fn canonicalize_sig_wrong_length_errors() {
        // Create a label value with a 32-byte sig (should be 64).
        let sig_value = Value::Bytes(vec![0u8; 32]);
        let mut value = Value::Map(vec![(Value::Text("sig".to_string()), sig_value)]);

        let result = extract_and_remove_sig(&mut value);
        assert!(matches!(
            result,
            Err(CanonicalizeError::SigFieldWrongLength { actual: 32 })
        ));
    }

    /// Test that parse_signature rejects invalid bytes without panicking.
    #[test]
    fn parse_signature_rejects_zero_scalar_without_panic() {
        use crate::common::identity::AnyVerifyingKey;
        use k256::ecdsa::SigningKey as K256SigningKey;

        // Create a k256 verifying key.
        let seed: [u8; 32] = [7u8; 32];
        let signing_key = K256SigningKey::from_slice(&seed).expect("valid secret scalar");
        let verifying_key = AnyVerifyingKey::K256(*signing_key.verifying_key());

        // Try to parse an invalid signature (64 zero bytes - invalid scalars).
        let invalid_sig_bytes = vec![0u8; 64];
        let result = parse_signature(&invalid_sig_bytes, &verifying_key);

        // Should return an error, not panic.
        assert!(result.is_err());
        match result.unwrap_err() {
            SignatureParseError::K256Failed { .. } => {
                // Expected error.
            }
            _ => panic!("Expected K256Failed error"),
        }
    }

    /// Golden test against real labeler output: constructs `Label`s from bytes captured
    /// from production labelers, canonicalizes them, and verifies the real wire signatures
    /// against the labelers' published `#atproto_label` multikeys. This is the load-bearing
    /// test for canonicalizer correctness — if the canonicalizer drifts from the spec, real
    /// signatures will fail to verify. Generated keys signing their own canonicalizer output
    /// cannot catch such drift because the bug is symmetric on sign and verify.
    ///
    /// Fixtures captured 2026-04-15 from:
    /// - moderation.bsky.app (did:plc:ar7c4by46qjdydhdevvrndac)
    /// - xblock.aendra.dev (did:plc:newitj5jo3uel7o4mnf3vj2o).
    #[test]
    fn canonicalizes_real_labeler_output_matches_wire_signature() {
        use crate::common::identity::parse_multikey;

        struct Fixture {
            name: &'static str,
            src: &'static str,
            uri: &'static str,
            cid: &'static str,
            val: &'static str,
            cts: &'static str,
            multikey: &'static str,
            sig: [u8; 64],
        }

        let fixtures = [
            Fixture {
                name: "moderation.bsky.app",
                src: "did:plc:ar7c4by46qjdydhdevvrndac",
                uri: "at://did:plc:gzdjlsa34b4jpbvegk4dngvb/app.bsky.feed.post/3m5p2kcpjek2t",
                cid: "bafyreihmigssl6hpegb3sfou5vemydbo63it5a253udvdoiae5cgfbc3jq",
                val: "sexual",
                cts: "2025-11-15T20:40:44.774Z",
                multikey: "zQ3shmV1BNcX17coaDbfen6zArEad6SCLT3jVWCbC6Y9iinTa",
                sig: [
                    0x18, 0xb9, 0xe5, 0xc2, 0x36, 0x87, 0x7e, 0x31, 0x17, 0x93, 0xc1, 0xe7, 0xbb,
                    0x82, 0xab, 0x78, 0x0d, 0x12, 0x7d, 0xb0, 0xf3, 0x80, 0x4b, 0x18, 0x6f, 0x1e,
                    0xeb, 0x77, 0xb8, 0xc7, 0xbd, 0x99, 0x30, 0x0b, 0x92, 0x85, 0xf7, 0xff, 0x3f,
                    0xa9, 0x8b, 0x43, 0xae, 0x1f, 0x1c, 0xf5, 0x22, 0x31, 0x9c, 0x70, 0x1e, 0x3e,
                    0x87, 0x69, 0xf6, 0x6e, 0x8e, 0x3f, 0x9c, 0x9c, 0x93, 0x18, 0x42, 0xf6,
                ],
            },
            Fixture {
                name: "xblock.aendra.dev",
                src: "did:plc:newitj5jo3uel7o4mnf3vj2o",
                uri: "at://did:plc:yioyxg6ym5gtda5yprh2p4c7/app.bsky.feed.post/3ld5mvbxqtk2p",
                cid: "bafyreiafpv7pn7z35dqcv3cbp44sw2efdakhnhxanibkm2q2jyo7u27ubq",
                val: "twitter-screenshot",
                cts: "2024-12-13T01:26:06.992Z",
                multikey: "zQ3shht8JUZuf87GTWQzmZKF1L61PEppz1aGjj7NrpNVmWz8H",
                sig: [
                    0x68, 0x21, 0x42, 0xb6, 0x7e, 0x95, 0x73, 0x9a, 0x18, 0x95, 0x3e, 0x86, 0x6e,
                    0x24, 0xc7, 0x8a, 0x33, 0x6f, 0xfd, 0x40, 0x25, 0xf7, 0xcd, 0xcc, 0x1b, 0x2e,
                    0x3d, 0x40, 0xef, 0x5b, 0xdd, 0xa7, 0x77, 0x31, 0x38, 0x9d, 0x54, 0x12, 0x52,
                    0xae, 0xdd, 0x18, 0x98, 0x85, 0xf5, 0xcc, 0xe6, 0x63, 0x3c, 0x6f, 0x21, 0xaf,
                    0xc8, 0x41, 0xa4, 0xd0, 0x6f, 0x7f, 0xf8, 0x0d, 0xb3, 0x8d, 0x08, 0x8d,
                ],
            },
        ];

        for fixture in &fixtures {
            let label: Label = LabelData {
                cid: Some(fixture.cid.parse().expect("valid cid")),
                cts: fixture.cts.parse().expect("valid datetime"),
                exp: None,
                neg: None,
                sig: Some(fixture.sig.to_vec()),
                src: fixture.src.parse().expect("valid did"),
                uri: fixture.uri.to_string(),
                val: fixture.val.to_string(),
                ver: Some(1),
            }
            .into();

            let canonical = canonicalize_label_for_signing(&label)
                .unwrap_or_else(|e| panic!("{}: canonicalize: {e}", fixture.name));

            assert_eq!(
                canonical.signature_bytes,
                fixture.sig.to_vec(),
                "{}: signature_bytes must round-trip through canonicalizer",
                fixture.name,
            );

            let parsed = parse_multikey(fixture.multikey)
                .unwrap_or_else(|e| panic!("{}: parse_multikey: {e}", fixture.name));
            assert!(
                matches!(parsed.verifying_key, AnyVerifyingKey::K256(_)),
                "{}: expected secp256k1 multikey",
                fixture.name,
            );

            let any_sig = AnySignature::K256(
                k256::ecdsa::Signature::from_slice(&fixture.sig)
                    .unwrap_or_else(|e| panic!("{}: parse signature: {e}", fixture.name)),
            );
            parsed
                .verifying_key
                .verify_prehash(&canonical.prehash, &any_sig)
                .unwrap_or_else(|e| {
                    panic!(
                        "{}: real labeler signature must verify against canonicalizer output: {e}",
                        fixture.name
                    )
                });
        }
    }
}
