use base64ct::{Base64UrlUnpadded, Encoding};
use chrono::{TimeZone, Utc};
use delegated::models::{
    AgentEndpoint, AgentIdentityDocument, DelegationToken, MaxSpend, PublicKeyRecord,
    RequestEnvelope, RuntimeContext, TrustProfile,
};
use delegated::{
    DelegationGrantProposal, FileBackedTrustState, TOKEN_SIGNATURE_ALG_ED25519, TrustStateStore,
    sign_delegation_token, sign_identity_document,
};
use ed25519_dalek::SigningKey;
use serde_json::Value;
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};

static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);

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

fn private_key_base64url() -> String {
    Base64UrlUnpadded::encode_string(&signing_key().to_bytes())
}

fn unique_id() -> String {
    let counter = REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("time should be after epoch")
        .as_nanos();
    format!("{counter}_{nanos}")
}

fn sample_token() -> DelegationToken {
    let unique_id = unique_id();
    DelegationToken {
        spec_version: "0.1".to_string(),
        kind: "DelegationToken".to_string(),
        token_id: format!("dlg_cli_{unique_id}"),
        issuer: "https://trust.example.ai".to_string(),
        agent_id: "agent:example:scheduler:v1".to_string(),
        delegator_id: "user:jake-abendroth".to_string(),
        owner_id: "org:example".to_string(),
        audience: vec!["tool:google-calendar".to_string()],
        allowed_actions: vec!["calendar.create_event".to_string()],
        resource_constraints: None,
        max_spend: None,
        max_delegation_depth: Some(0),
        issued_at: Utc
            .with_ymd_and_hms(2024, 6, 1, 20, 10, 0)
            .single()
            .expect("valid timestamp"),
        expires_at: Utc
            .with_ymd_and_hms(2099, 6, 1, 20, 40, 0)
            .single()
            .expect("valid timestamp"),
        intent: None,
        nonce: format!("nonce-cli-{unique_id}"),
        key_id: "key-2026-01".to_string(),
        signature_alg: TOKEN_SIGNATURE_ALG_ED25519.to_string(),
        signature: String::new(),
    }
}

fn signed_request_json() -> Value {
    let unique_id = unique_id();
    let key = signing_key();
    let mut identity_document = AgentIdentityDocument {
        spec_version: "0.1".to_string(),
        kind: "AgentIdentityDocument".to_string(),
        agent_id: "agent:example:scheduler:v1".to_string(),
        display_name: Some("example Scheduler Agent".to_string()),
        owner_id: "org:example".to_string(),
        issuer: "https://trust.example.ai".to_string(),
        identity_type: "spiffe".to_string(),
        subject: "spiffe://example.ai/agents/scheduler".to_string(),
        public_keys: vec![PublicKeyRecord {
            kid: "key-2026-01".to_string(),
            kty: "OKP".to_string(),
            crv: Some(TOKEN_SIGNATURE_ALG_ED25519.to_string()),
            x: Some(Base64UrlUnpadded::encode_string(
                &key.verifying_key().to_bytes(),
            )),
        }],
        supported_protocols: vec!["http".to_string()],
        supported_auth_methods: vec!["delegation_token".to_string()],
        capabilities: None,
        endpoints: vec![AgentEndpoint {
            protocol: "http".to_string(),
            url: "https://agents.example.ai/scheduler".to_string(),
        }],
        attestation: None,
        created_at: Utc
            .with_ymd_and_hms(2026, 6, 1, 20, 0, 0)
            .single()
            .expect("valid timestamp"),
        expires_at: Utc
            .with_ymd_and_hms(2026, 6, 8, 20, 0, 0)
            .single()
            .expect("valid timestamp"),
        signature: String::new(),
    };
    identity_document.signature =
        sign_identity_document(&identity_document, &key).expect("identity signing should work");

    let mut token = sample_token();
    token.signature = sign_delegation_token(&token, &key).expect("token signing should work");

    let request = RequestEnvelope {
        spec_version: "0.1".to_string(),
        kind: "TrustRequestEnvelope".to_string(),
        request_id: Some(format!("req_cli_verify_{unique_id}")),
        profile: TrustProfile::Developer,
        agent_id: "agent:example:scheduler:v1".to_string(),
        delegator_id: "user:jake-abendroth".to_string(),
        audience: "tool:google-calendar".to_string(),
        action: "calendar.create_event".to_string(),
        resource: None,
        runtime_context: RuntimeContext::default(),
        identity_document: Some(identity_document),
        token,
    };

    serde_json::to_value(request).expect("request serialization should work")
}

fn sample_proposal() -> DelegationGrantProposal {
    DelegationGrantProposal {
        request_id: format!("req_cli_grant_{}", unique_id()),
        delegator_id: "user:jake-abendroth".to_string(),
        agent_id: "agent:example:scheduler:v1".to_string(),
        owner_id: "org:example".to_string(),
        intent: "schedule_demo".to_string(),
        audience: vec!["tool:google-calendar".to_string()],
        allowed_actions: vec!["calendar.create_event".to_string()],
        max_spend: Some(MaxSpend {
            amount: 0,
            currency: "USD".to_string(),
        }),
        expires_at: Utc
            .with_ymd_and_hms(2099, 6, 1, 20, 40, 0)
            .single()
            .expect("valid timestamp"),
    }
}

#[test]
fn cli_signs_token_with_ed25519() {
    let temp_dir = std::env::temp_dir().join(format!(
        "delegated_cli_sign_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time should be after epoch")
            .as_nanos()
    ));
    std::fs::create_dir_all(&temp_dir).expect("temp directory should be creatable");
    let token_path = temp_dir.join("token.json");
    std::fs::write(
        &token_path,
        serde_json::to_string_pretty(&sample_token()).expect("token serialization should work"),
    )
    .expect("token file should be writable");

    let output = Command::new(env!("CARGO_BIN_EXE_delegated-cli"))
        .arg("sign-token")
        .arg(token_path.to_string_lossy().to_string())
        .arg(private_key_base64url())
        .output()
        .expect("CLI should execute");

    assert!(output.status.success());
    let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
    let signed: DelegationToken = serde_json::from_str(&stdout).expect("signed token should parse");
    assert_eq!(signed.signature_alg, TOKEN_SIGNATURE_ALG_ED25519);
    assert!(!signed.signature.is_empty());

    std::fs::remove_dir_all(temp_dir).expect("temp directory should be removable");
}

#[test]
fn cli_verifies_request_and_returns_success_exit_code() {
    let temp_dir = std::env::temp_dir().join(format!(
        "delegated_cli_verify_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time should be after epoch")
            .as_nanos()
    ));
    std::fs::create_dir_all(&temp_dir).expect("temp directory should be creatable");
    let request_path = temp_dir.join("request.json");
    std::fs::write(
        &request_path,
        serde_json::to_string_pretty(&signed_request_json())
            .expect("request serialization should work"),
    )
    .expect("request file should be writable");

    let output = Command::new(env!("CARGO_BIN_EXE_delegated-cli"))
        .arg("verify-request")
        .arg(request_path.to_string_lossy().to_string())
        .output()
        .expect("CLI should execute");

    assert!(output.status.success());
    let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
    assert!(stdout.contains("\"allowed\": true"));

    std::fs::remove_dir_all(temp_dir).expect("temp directory should be removable");
}

#[test]
fn cli_approves_grant_and_emits_callback_payload() {
    let temp_dir = std::env::temp_dir().join(format!(
        "delegated_cli_approve_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time should be after epoch")
            .as_nanos()
    ));
    std::fs::create_dir_all(&temp_dir).expect("temp directory should be creatable");
    let proposal_path = temp_dir.join("proposal.json");
    let proposal = sample_proposal();
    std::fs::write(
        &proposal_path,
        serde_json::to_string_pretty(&proposal).expect("proposal serialization should work"),
    )
    .expect("proposal file should be writable");

    let output = Command::new(env!("CARGO_BIN_EXE_delegated-cli"))
        .arg("approve-grant")
        .arg(proposal_path.to_string_lossy().to_string())
        .arg("approve")
        .arg("user:jake-abendroth")
        .arg("--reason")
        .arg("approved")
        .arg("--token-id")
        .arg("dlg_cli_grant")
        .output()
        .expect("CLI should execute");

    assert!(output.status.success());
    let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
    let payload: Value = serde_json::from_str(&stdout).expect("approve payload should parse");
    assert_eq!(
        payload["operation"]["receipt"]["status"],
        serde_json::json!("Approved")
    );
    assert_eq!(
        payload["operation"]["callback"]["request_id"],
        serde_json::json!(proposal.request_id)
    );

    std::fs::remove_dir_all(temp_dir).expect("temp directory should be removable");
}

#[test]
fn cli_revokes_token_and_persists_state_update() {
    let temp_dir = std::env::temp_dir().join(format!(
        "delegated_cli_revoke_{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("time should be after epoch")
            .as_nanos()
    ));
    std::fs::create_dir_all(&temp_dir).expect("temp directory should be creatable");
    let state_path = temp_dir.join("trust-state.json");
    let token_id = format!("dlg_cli_revoke_{}", unique_id());
    let output = Command::new(env!("CARGO_BIN_EXE_delegated-cli"))
        .env(
            "DELEGATED_TRUST_STATE_PATH",
            state_path.to_string_lossy().to_string(),
        )
        .arg("revoke-token")
        .arg("req_cli_revoke")
        .arg(&token_id)
        .arg("user:jake-abendroth")
        .arg("--reason")
        .arg("manual revoke")
        .output()
        .expect("CLI should execute");

    assert!(output.status.success());
    let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
    let payload: Value = serde_json::from_str(&stdout).expect("revoke payload should parse");
    assert_eq!(payload["receipt"]["status"], serde_json::json!("Revoked"));
    let state = FileBackedTrustState::new(state_path);
    assert!(
        state
            .is_token_revoked(&token_id)
            .expect("state query should succeed")
    );

    std::fs::remove_dir_all(temp_dir).expect("temp directory should be removable");
}
