//! Generates committed 0.2 reference vectors for cross-language conformance testing.
//!
//! Run: `cargo run --example gen_vectors > tests/fixtures/vectors.json`

use base64ct::{Base64UrlUnpadded, Encoding};
use chrono::{Duration, Utc};
use delegated::{DelegationTokenBuilder, KIND_DELEGATION_TOKEN, SPEC_VERSION_CURRENT, envelope};
use ed25519_dalek::{SigningKey, VerifyingKey};
use serde::Serialize;
use serde_json::{Value, json};

const EVALUATE_AT: &str = "2026-07-01T12:00:00Z";
const ISSUER: &str = "https://issuer.example";
const KEY_ID: &str = "issuer-1";

fn evaluate_at() -> chrono::DateTime<Utc> {
    EVALUATE_AT.parse().unwrap()
}

fn signing_key() -> SigningKey {
    SigningKey::from_bytes(&[0x42; 32])
}

fn public_key_b64(key: &VerifyingKey) -> String {
    Base64UrlUnpadded::encode_string(key.as_bytes())
}

fn signed_envelope(
    key: &SigningKey,
    token_id: &str,
    nonce: &str,
    issued_at: chrono::DateTime<Utc>,
    expires_at: chrono::DateTime<Utc>,
    request_id: Option<&str>,
) -> Value {
    let token = DelegationTokenBuilder::new()
        .token_id(token_id)
        .issuer(ISSUER)
        .agent_id("agent:scheduler")
        .delegator_id("user:alice")
        .audience("calendar-api")
        .allowed_action("calendar.create")
        .allowed_resource("calendar:alice")
        .max_delegation_depth(0)
        .issued_at(issued_at)
        .expires_at(expires_at)
        .nonce(nonce)
        .key_id(KEY_ID)
        .build_and_sign(key)
        .expect("fixture token must build");
    serde_json::to_value(envelope(token, request_id.map(str::to_owned))).unwrap()
}

#[derive(Serialize)]
struct Manifest {
    spec_version: &'static str,
    description: &'static str,
    evaluate_at: &'static str,
    signing_key: SigningKeyInfo,
    trusted_issuer: &'static str,
    trusted_key_id: &'static str,
    vectors: Vec<Vector>,
}

#[derive(Serialize)]
struct SigningKeyInfo {
    algorithm: &'static str,
    seed_hex: &'static str,
    public_key_b64url: String,
}

#[derive(Serialize)]
struct Vector {
    id: &'static str,
    description: &'static str,
    operation: OperationSpec,
    #[serde(skip_serializing_if = "Option::is_none")]
    trust_state: Option<TrustStateSpec>,
    #[serde(skip_serializing_if = "Option::is_none")]
    steps: Option<Vec<StepSpec>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    envelope: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    raw_request: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    expected: Option<ExpectedSpec>,
}

#[derive(Serialize, Clone)]
struct OperationSpec {
    audience: &'static str,
    action: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    resource: Option<&'static str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    delegation_depth: Option<u16>,
}

#[derive(Serialize)]
struct TrustStateSpec {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    revoke_tokens: Vec<[&'static str; 2]>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    deny_agents: Vec<[&'static str; 2]>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    preconsume_nonces: Vec<[&'static str; 2]>,
}

#[derive(Serialize)]
struct StepSpec {
    #[serde(skip_serializing_if = "Option::is_none")]
    envelope: Option<Value>,
    expected: ExpectedSpec,
}

#[derive(Serialize, Clone)]
struct ExpectedSpec {
    allowed: bool,
    stage: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    reason_contains: Option<&'static str>,
}

fn main() {
    let key = signing_key();
    let now = evaluate_at();
    let operation = OperationSpec {
        audience: "calendar-api",
        action: "calendar.create",
        resource: Some("calendar:alice"),
        delegation_depth: Some(0),
    };

    let allow_envelope = signed_envelope(
        &key,
        "token-allow",
        "nonce-allow-00000001",
        now - Duration::minutes(1),
        now + Duration::minutes(10),
        Some("req-allow"),
    );

    let mut tampered = allow_envelope.clone();
    tampered["delegation_token"]["agent_id"] = json!("agent:attacker");

    let expired = signed_envelope(
        &key,
        "token-expired",
        "nonce-expired-000001",
        now - Duration::hours(2),
        now - Duration::hours(1),
        None,
    );

    let future = signed_envelope(
        &key,
        "token-future",
        "nonce-future-0000001",
        now + Duration::hours(1),
        now + Duration::hours(2),
        None,
    );

    let too_long = signed_envelope(
        &key,
        "token-too-long",
        "nonce-too-long-00001",
        now - Duration::minutes(1),
        now + Duration::hours(25),
        None,
    );

    let wrong_version = {
        let mut envelope = signed_envelope(
            &key,
            "token-bad-version",
            "nonce-bad-version-01",
            now - Duration::minutes(1),
            now + Duration::minutes(10),
            None,
        );
        envelope["spec_version"] = json!("0.1");
        envelope
    };

    let unknown_field = {
        let mut envelope = allow_envelope.clone();
        envelope["unexpected"] = json!("value");
        envelope
    };

    let manifest = Manifest {
        spec_version: SPEC_VERSION_CURRENT,
        description: "Delegated 0.2 reference vectors for evaluator conformance",
        evaluate_at: EVALUATE_AT,
        signing_key: SigningKeyInfo {
            algorithm: "Ed25519",
            seed_hex: "4242424242424242424242424242424242424242424242424242424242424242",
            public_key_b64url: public_key_b64(&key.verifying_key()),
        },
        trusted_issuer: ISSUER,
        trusted_key_id: KEY_ID,
        vectors: vec![
            Vector {
                id: "allow-basic",
                description: "Valid capability authorizes the matching host operation",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(allow_envelope.clone()),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: true,
                    stage: "authorized",
                    reason_contains: None,
                }),
            },
            Vector {
                id: "deny-untrusted-issuer",
                description: "Verifier without a pinned issuer key denies",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(allow_envelope.clone()),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "verify_issuer",
                    reason_contains: Some("not trusted"),
                }),
            },
            Vector {
                id: "deny-tampered-claims",
                description: "Modified claims fail signature verification",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(tampered),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "verify_issuer",
                    reason_contains: Some("signature"),
                }),
            },
            Vector {
                id: "deny-expired",
                description: "Expired capabilities are rejected",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(expired),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "validate_lifetime",
                    reason_contains: Some("expired"),
                }),
            },
            Vector {
                id: "deny-not-yet-active",
                description: "Future-dated capabilities are rejected",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(future),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "validate_lifetime",
                    reason_contains: Some("not active"),
                }),
            },
            Vector {
                id: "deny-lifetime-too-long",
                description: "Capabilities exceeding max lifetime are rejected",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(too_long),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "validate_lifetime",
                    reason_contains: Some("lifetime"),
                }),
            },
            Vector {
                id: "deny-wrong-action",
                description: "Host operation action must match token allowlist",
                operation: OperationSpec {
                    action: "calendar.delete",
                    ..operation.clone()
                },
                trust_state: None,
                steps: None,
                envelope: Some(allow_envelope.clone()),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "bind_operation",
                    reason_contains: Some("action"),
                }),
            },
            Vector {
                id: "deny-missing-resource",
                description: "Constrained tokens require a host resource",
                operation: OperationSpec {
                    resource: None,
                    ..operation.clone()
                },
                trust_state: None,
                steps: None,
                envelope: Some(allow_envelope.clone()),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "bind_operation",
                    reason_contains: Some("resource is required"),
                }),
            },
            Vector {
                id: "deny-revoked-token",
                description: "Revoked token identifiers are denied",
                operation: operation.clone(),
                trust_state: Some(TrustStateSpec {
                    revoke_tokens: vec![[ISSUER, "token-allow"]],
                    deny_agents: vec![],
                    preconsume_nonces: vec![],
                }),
                steps: None,
                envelope: Some(allow_envelope.clone()),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "revocation",
                    reason_contains: Some("revoked"),
                }),
            },
            Vector {
                id: "deny-denied-agent",
                description: "Emergency-denied agents are rejected",
                operation: operation.clone(),
                trust_state: Some(TrustStateSpec {
                    revoke_tokens: vec![],
                    deny_agents: vec![[ISSUER, "agent:scheduler"]],
                    preconsume_nonces: vec![],
                }),
                steps: None,
                envelope: Some(allow_envelope.clone()),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "revocation",
                    reason_contains: Some("denied"),
                }),
            },
            Vector {
                id: "deny-replay",
                description: "Second use of the same nonce is rejected",
                operation: operation.clone(),
                trust_state: None,
                steps: Some(vec![
                    StepSpec {
                        envelope: Some(allow_envelope.clone()),
                        expected: ExpectedSpec {
                            allowed: true,
                            stage: "authorized",
                            reason_contains: None,
                        },
                    },
                    StepSpec {
                        envelope: Some(allow_envelope.clone()),
                        expected: ExpectedSpec {
                            allowed: false,
                            stage: "replay",
                            reason_contains: Some("nonce"),
                        },
                    },
                ]),
                envelope: None,
                raw_request: None,
                expected: None,
            },
            Vector {
                id: "deny-unknown-fields",
                description: "Unknown envelope fields are rejected",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(unknown_field),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "normalize_request",
                    reason_contains: None,
                }),
            },
            Vector {
                id: "deny-unsupported-version",
                description: "Unsupported spec versions are rejected",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: Some(wrong_version),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "normalize_request",
                    reason_contains: Some("spec_version"),
                }),
            },
            Vector {
                id: "deny-oversized-envelope",
                description: "Oversized requests are rejected before parsing",
                operation: operation.clone(),
                trust_state: None,
                steps: None,
                envelope: None,
                raw_request: Some(" ".repeat(delegated::DEFAULT_MAX_ENVELOPE_BYTES + 1)),
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "normalize_request",
                    reason_contains: Some("byte limit"),
                }),
            },
            Vector {
                id: "deny-invalid-token-kind",
                description: "Wrong token kind is rejected during normalization",
                operation,
                trust_state: None,
                steps: None,
                envelope: Some({
                    let mut envelope = allow_envelope;
                    envelope["delegation_token"]["kind"] = json!(KIND_DELEGATION_TOKEN);
                    envelope["delegation_token"]["kind"] = json!("WrongCapability");
                    envelope
                }),
                raw_request: None,
                expected: Some(ExpectedSpec {
                    allowed: false,
                    stage: "normalize_request",
                    reason_contains: Some("kind"),
                }),
            },
        ],
    };

    println!(
        "{}",
        serde_json::to_string_pretty(&manifest).expect("manifest must serialize")
    );
}
