use std::collections::BTreeMap;

use chrono::Utc;
use percent_encoding::percent_decode_str;
use rand::rngs::OsRng;
use regex::Regex;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use sha2::{Digest, Sha256};
use thiserror::Error;

use crate::canonical_json::canonicalize_json;
use crate::keys::{base64url_encode, GeneratedKeyPairPem};
use crate::proof::{
    generate_w3c_proof, verify_w3c_proof, ProofGenerationOptions,
    ProofVerificationOptions, CRYPTOSUITE_DIDWBA_SECP256K1_2025,
    CRYPTOSUITE_EDDSA_JCS_2022, PROOF_TYPE_DATA_INTEGRITY,
    PROOF_TYPE_SECP256K1,
};
use crate::{PrivateKeyMaterial, PublicKeyMaterial};

use super::verification_methods::{create_verification_method, extract_public_key};

pub const VM_KEY_AUTH: &str = "key-1";
pub const VM_KEY_E2EE_SIGNING: &str = "key-2";
pub const VM_KEY_E2EE_AGREEMENT: &str = "key-3";

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum DidProfile {
    #[serde(rename = "e1")]
    E1,
    #[serde(rename = "k1")]
    K1,
    #[serde(rename = "plain_legacy")]
    PlainLegacy,
}

impl Default for DidProfile {
    fn default() -> Self {
        Self::E1
    }
}

impl DidProfile {
    pub fn from_str(value: &str) -> Result<Self, AuthenticationError> {
        match value.to_ascii_lowercase().as_str() {
            "e1" => Ok(Self::E1),
            "k1" => Ok(Self::K1),
            "plain_legacy" => Ok(Self::PlainLegacy),
            _ => Err(AuthenticationError::UnsupportedProfile),
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            Self::E1 => "e1",
            Self::K1 => "k1",
            Self::PlainLegacy => "plain_legacy",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DidDocumentOptions {
    pub port: Option<u16>,
    pub path_segments: Vec<String>,
    pub agent_description_url: Option<String>,
    pub services: Vec<Value>,
    pub proof_purpose: String,
    pub verification_method: Option<String>,
    pub domain: Option<String>,
    pub challenge: Option<String>,
    pub created: Option<String>,
    pub enable_e2ee: bool,
    pub did_profile: DidProfile,
}

impl Default for DidDocumentOptions {
    fn default() -> Self {
        Self {
            port: None,
            path_segments: Vec::new(),
            agent_description_url: None,
            services: Vec::new(),
            proof_purpose: "assertionMethod".to_string(),
            verification_method: None,
            domain: None,
            challenge: None,
            created: None,
            enable_e2ee: true,
            did_profile: DidProfile::E1,
        }
    }
}

impl DidDocumentOptions {
    pub fn with_profile(mut self, profile: DidProfile) -> Self {
        self.did_profile = profile;
        self
    }

    pub fn with_path_segments<I, S>(mut self, path_segments: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.path_segments = path_segments.into_iter().map(Into::into).collect();
        self
    }

    pub fn with_agent_description_url(mut self, value: impl Into<String>) -> Self {
        self.agent_description_url = Some(value.into());
        self
    }

    pub fn with_service(mut self, service: Value) -> Self {
        self.services.push(service);
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DidDocumentBundle {
    pub did_document: Value,
    pub keys: BTreeMap<String, GeneratedKeyPairPem>,
}

impl DidDocumentBundle {
    pub fn did(&self) -> Option<&str> {
        self.did_document.get("id").and_then(Value::as_str)
    }

    pub fn private_key_pem(&self, fragment: &str) -> Option<&str> {
        self.keys.get(fragment).map(|value| value.private_key_pem.as_str())
    }

    pub fn public_key_pem(&self, fragment: &str) -> Option<&str> {
        self.keys.get(fragment).map(|value| value.public_key_pem.as_str())
    }

    pub fn load_private_key(&self, fragment: &str) -> Result<PrivateKeyMaterial, AuthenticationError> {
        let pem = self.private_key_pem(fragment).ok_or(AuthenticationError::InvalidDidDocument)?;
        PrivateKeyMaterial::from_pem(pem).map_err(|_| AuthenticationError::InvalidDidDocument)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedAuthHeader {
    pub did: String,
    pub nonce: String,
    pub timestamp: String,
    pub verification_method: String,
    pub signature: String,
    pub version: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DidResolutionOptions {
    pub timeout_seconds: f64,
    pub verify_ssl: bool,
    pub base_url_override: Option<String>,
}

impl Default for DidResolutionOptions {
    fn default() -> Self {
        Self {
            timeout_seconds: 10.0,
            verify_ssl: true,
            base_url_override: None,
        }
    }
}

#[derive(Debug, Error)]
pub enum AuthenticationError {
    #[error("Hostname cannot be empty")]
    EmptyHostname,
    #[error("Hostname cannot be an IP address")]
    IpAddressNotAllowed,
    #[error("Invalid DID format")]
    InvalidDid,
    #[error("Invalid DID document")]
    InvalidDidDocument,
    #[error("DID binding verification failed")]
    InvalidDidBinding,
    #[error("Authentication header must start with DIDWba")]
    InvalidAuthorizationHeader,
    #[error("Missing field in authorization header: {0}")]
    MissingAuthorizationField(String),
    #[error("Verification method not found")]
    VerificationMethodNotFound,
    #[error("Verification failed")]
    VerificationFailed,
    #[error("Signature generation failed")]
    SignatureGenerationFailed,
    #[error("Network failure")]
    NetworkFailure,
    #[error("I/O failure")]
    IoFailure,
    #[error("JSON failure")]
    JsonFailure,
    #[error("Regex failure")]
    RegexFailure,
    #[error("Unsupported profile")]
    UnsupportedProfile,
    #[error("Verification method error: {0}")]
    VerificationMethod(String),
    #[error("Proof error: {0}")]
    Proof(String),
}

pub fn create_did_wba_document(
    hostname: &str,
    options: DidDocumentOptions,
) -> Result<DidDocumentBundle, AuthenticationError> {
    if hostname.trim().is_empty() {
        return Err(AuthenticationError::EmptyHostname);
    }
    if is_ip_address(hostname) {
        return Err(AuthenticationError::IpAddressNotAllowed);
    }

    let did_base = build_did_base(hostname, options.port);
    let mut path_segments = options.path_segments.clone();
    let mut contexts = vec![Value::String(
        "https://www.w3.org/ns/did/v1".to_string(),
    )];
    let mut verification_methods: Vec<Value> = Vec::new();
    let mut authentication_entries: Vec<Value> = Vec::new();
    let mut assertion_method_entries: Vec<Value> = Vec::new();
    let mut key_agreement_entries: Vec<Value> = Vec::new();
    let mut keys = BTreeMap::new();

    let auth_private_key = match options.did_profile {
        DidProfile::E1 => {
            PrivateKeyMaterial::Ed25519(ed25519_dalek::SigningKey::generate(&mut OsRng))
        }
        DidProfile::K1 | DidProfile::PlainLegacy => {
            PrivateKeyMaterial::Secp256k1(k256::ecdsa::SigningKey::random(&mut OsRng))
        }
    };
    let auth_public_key = auth_private_key.public_key();

    let did = match options.did_profile {
        DidProfile::E1 => {
            if !path_segments.is_empty() {
                path_segments.push(format!(
                    "e1_{}",
                    compute_multikey_fingerprint(&auth_public_key)?
                ));
            }
            join_did(&did_base, &path_segments)
        }
        DidProfile::K1 => {
            if !path_segments.is_empty() {
                path_segments.push(format!(
                    "k1_{}",
                    compute_jwk_fingerprint(&auth_public_key)?
                ));
            }
            join_did(&did_base, &path_segments)
        }
        DidProfile::PlainLegacy => join_did(&did_base, &path_segments),
    };

    let auth_vm = match (&options.did_profile, &auth_public_key) {
        (DidProfile::E1, PublicKeyMaterial::Ed25519(key)) => {
            contexts.push(Value::String(
                "https://w3id.org/security/data-integrity/v2".to_string(),
            ));
            contexts.push(Value::String(
                "https://w3id.org/security/multikey/v1".to_string(),
            ));
            json!({
                "id": format!("{did}#{VM_KEY_AUTH}"),
                "type": "Multikey",
                "controller": did,
                "publicKeyMultibase": ed25519_public_key_to_multibase(key),
            })
        }
        (DidProfile::K1, PublicKeyMaterial::Secp256k1(key))
        | (DidProfile::PlainLegacy, PublicKeyMaterial::Secp256k1(key)) => {
            contexts.push(Value::String(
                "https://w3id.org/security/suites/jws-2020/v1".to_string(),
            ));
            contexts.push(Value::String(
                "https://w3id.org/security/suites/secp256k1-2019/v1".to_string(),
            ));
            if matches!(options.did_profile, DidProfile::K1) {
                contexts.push(Value::String(
                    "https://w3id.org/security/data-integrity/v2".to_string(),
                ));
            }
            json!({
                "id": format!("{did}#{VM_KEY_AUTH}"),
                "type": "EcdsaSecp256k1VerificationKey2019",
                "controller": did,
                "publicKeyJwk": secp256k1_public_key_to_jwk(key)?,
            })
        }
        _ => return Err(AuthenticationError::UnsupportedProfile),
    };

    verification_methods.push(auth_vm.clone());
    authentication_entries.push(Value::String(format!("{did}#{VM_KEY_AUTH}")));
    if matches!(options.did_profile, DidProfile::E1 | DidProfile::K1) {
        assertion_method_entries.push(Value::String(format!("{did}#{VM_KEY_AUTH}")));
    }
    keys.insert(
        VM_KEY_AUTH.to_string(),
        GeneratedKeyPairPem {
            private_key_pem: auth_private_key.to_pem(),
            public_key_pem: auth_public_key.to_pem(),
        },
    );

    if options.enable_e2ee {
        contexts.push(Value::String(
            "https://w3id.org/security/suites/x25519-2019/v1".to_string(),
        ));
        let signing_key =
            PrivateKeyMaterial::Secp256r1(p256::ecdsa::SigningKey::random(&mut OsRng));
        let agreement_key =
            PrivateKeyMaterial::X25519(x25519_dalek::StaticSecret::from(rand::random::<
                [u8; 32],
            >()));
        let signing_public = signing_key.public_key();
        let agreement_public = agreement_key.public_key();

        let signing_vm = match &signing_public {
            PublicKeyMaterial::Secp256r1(key) => json!({
                "id": format!("{did}#{VM_KEY_E2EE_SIGNING}"),
                "type": "EcdsaSecp256r1VerificationKey2019",
                "controller": did,
                "publicKeyJwk": secp256r1_public_key_to_jwk(key)?,
            }),
            _ => return Err(AuthenticationError::InvalidDidDocument),
        };
        let agreement_vm = match &agreement_public {
            PublicKeyMaterial::X25519(bytes) => json!({
                "id": format!("{did}#{VM_KEY_E2EE_AGREEMENT}"),
                "type": "X25519KeyAgreementKey2019",
                "controller": did,
                "publicKeyMultibase": x25519_public_key_to_multibase(bytes),
            }),
            _ => return Err(AuthenticationError::InvalidDidDocument),
        };

        verification_methods.push(signing_vm);
        verification_methods.push(agreement_vm);
        key_agreement_entries.push(Value::String(format!(
            "{did}#{VM_KEY_E2EE_AGREEMENT}"
        )));
        keys.insert(
            VM_KEY_E2EE_SIGNING.to_string(),
            GeneratedKeyPairPem {
                private_key_pem: signing_key.to_pem(),
                public_key_pem: signing_public.to_pem(),
            },
        );
        keys.insert(
            VM_KEY_E2EE_AGREEMENT.to_string(),
            GeneratedKeyPairPem {
                private_key_pem: agreement_key.to_pem(),
                public_key_pem: agreement_public.to_pem(),
            },
        );
    }

    let mut document = Map::new();
    document.insert("@context".to_string(), Value::Array(contexts));
    document.insert("id".to_string(), Value::String(did.clone()));
    document.insert(
        "verificationMethod".to_string(),
        Value::Array(verification_methods),
    );
    document.insert(
        "authentication".to_string(),
        Value::Array(authentication_entries),
    );
    if !assertion_method_entries.is_empty() {
        document.insert(
            "assertionMethod".to_string(),
            Value::Array(assertion_method_entries),
        );
    }
    if !key_agreement_entries.is_empty() {
        document.insert(
            "keyAgreement".to_string(),
            Value::Array(key_agreement_entries),
        );
    }
    let services = build_service_entries(
        &did,
        options.agent_description_url.as_deref(),
        &options.services,
    );
    if !services.is_empty() {
        document.insert("service".to_string(), Value::Array(services));
    }

    let proof_options = ProofGenerationOptions {
        proof_purpose: Some(options.proof_purpose.clone()),
        proof_type: Some(match options.did_profile {
            DidProfile::E1 | DidProfile::K1 => PROOF_TYPE_DATA_INTEGRITY.to_string(),
            DidProfile::PlainLegacy => PROOF_TYPE_SECP256K1.to_string(),
        }),
        cryptosuite: match options.did_profile {
            DidProfile::E1 => Some(CRYPTOSUITE_EDDSA_JCS_2022.to_string()),
            DidProfile::K1 => Some(CRYPTOSUITE_DIDWBA_SECP256K1_2025.to_string()),
            DidProfile::PlainLegacy => None,
        },
        created: options.created.clone(),
        domain: options.domain.clone(),
        challenge: options.challenge.clone(),
    };
    let verification_method = options
        .verification_method
        .clone()
        .unwrap_or_else(|| format!("{did}#{VM_KEY_AUTH}"));
    let signed_document = generate_w3c_proof(
        &Value::Object(document),
        &auth_private_key,
        &verification_method,
        proof_options,
    )
    .map_err(|err| AuthenticationError::Proof(err.to_string()))?;

    Ok(DidDocumentBundle {
        did_document: signed_document,
        keys,
    })
}

pub fn create_did_wba_document_with_key_binding(
    hostname: &str,
    mut options: DidDocumentOptions,
) -> Result<DidDocumentBundle, AuthenticationError> {
    if options.path_segments.is_empty() {
        options.path_segments = vec!["user".to_string()];
    }
    options.did_profile = DidProfile::K1;
    create_did_wba_document(hostname, options)
}

pub fn compute_jwk_fingerprint(
    public_key: &PublicKeyMaterial,
) -> Result<String, AuthenticationError> {
    match public_key {
        PublicKeyMaterial::Secp256k1(key) => jwk_thumbprint(&secp256k1_public_key_to_jwk(key)?),
        _ => Err(AuthenticationError::InvalidDidDocument),
    }
}

pub fn compute_multikey_fingerprint(
    public_key: &PublicKeyMaterial,
) -> Result<String, AuthenticationError> {
    match public_key {
        PublicKeyMaterial::Ed25519(key) => {
            let jwk = json!({
                "crv": "Ed25519",
                "kty": "OKP",
                "x": base64url_encode(&key.to_bytes()),
            });
            jwk_thumbprint(&jwk)
        }
        _ => Err(AuthenticationError::InvalidDidDocument),
    }
}

pub fn verify_did_key_binding(did: &str, binding_material: &Value) -> bool {
    let last_segment = did.rsplit(':').next().unwrap_or_default();
    if let Some(expected) = last_segment.strip_prefix("k1_") {
        if let Ok(public_key) = extract_public_key(binding_material) {
            return compute_jwk_fingerprint(&public_key)
                .map(|value| value == expected)
                .unwrap_or(false);
        }
        return false;
    }
    if let Some(expected) = last_segment.strip_prefix("e1_") {
        if let Ok(public_key) = extract_public_key(binding_material) {
            return compute_multikey_fingerprint(&public_key)
                .map(|value| value == expected)
                .unwrap_or(false);
        }
        return false;
    }
    true
}

pub fn validate_did_document_binding(did_document: &Value, verify_proof: bool) -> bool {
    let did = did_document.get("id").and_then(Value::as_str).unwrap_or_default();
    let last_segment = did.rsplit(':').next().unwrap_or_default();
    if let Some(expected) = last_segment.strip_prefix("e1_") {
        return validate_e1_binding(did_document, expected);
    }
    if let Some(expected) = last_segment.strip_prefix("k1_") {
        if verify_proof {
            return validate_k1_binding(did_document, expected);
        }
        if let Some(methods) = did_document
            .get("verificationMethod")
            .and_then(Value::as_array)
        {
            for method in methods {
                if let Some(method_id) = method.get("id").and_then(Value::as_str) {
                    if is_authentication_authorized(did_document, method_id)
                        && verify_did_key_binding(did, method)
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    true
}

pub async fn resolve_did_wba_document(
    did: &str,
    verify_proof: bool,
) -> Result<Value, AuthenticationError> {
    resolve_did_wba_document_with_options(did, verify_proof, &DidResolutionOptions::default())
        .await
}

pub async fn resolve_did_wba_document_with_options(
    did: &str,
    verify_proof: bool,
    options: &DidResolutionOptions,
) -> Result<Value, AuthenticationError> {
    if !did.starts_with("did:wba:") {
        return Err(AuthenticationError::InvalidDid);
    }
    let parts: Vec<&str> = did.split(':').collect();
    if parts.len() < 3 {
        return Err(AuthenticationError::InvalidDid);
    }
    let encoded_domain = parts[2];
    let domain = percent_decode_str(encoded_domain)
        .decode_utf8_lossy()
        .to_string();
    let path_segments = if parts.len() > 3 { &parts[3..] } else { &[][..] };

    let base_url = options
        .base_url_override
        .clone()
        .unwrap_or_else(|| format!("https://{domain}"));
    let url = if path_segments.is_empty() {
        format!("{}/.well-known/did.json", base_url.trim_end_matches('/'))
    } else {
        format!(
            "{}/{}/did.json",
            base_url.trim_end_matches('/'),
            path_segments.join("/")
        )
    };

    let client = Client::builder()
        .danger_accept_invalid_certs(!options.verify_ssl)
        .timeout(std::time::Duration::from_secs_f64(options.timeout_seconds))
        .build()
        .map_err(|_| AuthenticationError::NetworkFailure)?;

    let response = client
        .get(url)
        .header("Accept", "application/json")
        .send()
        .await
        .map_err(|_| AuthenticationError::NetworkFailure)?
        .error_for_status()
        .map_err(|_| AuthenticationError::NetworkFailure)?;
    let document: Value = response
        .json()
        .await
        .map_err(|_| AuthenticationError::JsonFailure)?;

    if document.get("id").and_then(Value::as_str) != Some(did) {
        return Err(AuthenticationError::InvalidDidDocument);
    }
    if !validate_did_document_binding(&document, verify_proof) {
        return Err(AuthenticationError::InvalidDidBinding);
    }
    if verify_proof {
        let proof = document
            .get("proof")
            .ok_or(AuthenticationError::InvalidDidDocument)?;
        let verification_method = proof
            .get("verificationMethod")
            .and_then(Value::as_str)
            .ok_or(AuthenticationError::InvalidDidDocument)?;
        let method = find_verification_method(&document, verification_method)
            .ok_or(AuthenticationError::VerificationMethodNotFound)?;
        let public_key = extract_public_key(&method)
            .map_err(|err| AuthenticationError::VerificationMethod(err.to_string()))?;
        if !verify_w3c_proof(
            &document,
            &public_key,
            ProofVerificationOptions::default(),
        ) {
            return Err(AuthenticationError::VerificationFailed);
        }
    }

    Ok(document)
}

pub fn resolve_did_wba_document_sync(
    did: &str,
    verify_proof: bool,
) -> Result<Value, AuthenticationError> {
    let runtime = tokio::runtime::Runtime::new()
        .map_err(|_| AuthenticationError::NetworkFailure)?;
    runtime.block_on(resolve_did_wba_document(did, verify_proof))
}

pub fn generate_auth_header(
    did_document: &Value,
    service_domain: &str,
    private_key: &PrivateKeyMaterial,
    version: &str,
) -> Result<String, AuthenticationError> {
    let parsed = generate_auth_payload(
        did_document,
        service_domain,
        private_key,
        version,
        None,
        None,
    )?;
    Ok(format!(
        "DIDWba v=\"{}\", did=\"{}\", nonce=\"{}\", timestamp=\"{}\", verification_method=\"{}\", signature=\"{}\"",
        parsed.version,
        parsed.did,
        parsed.nonce,
        parsed.timestamp,
        parsed.verification_method,
        parsed.signature,
    ))
}

pub fn generate_auth_json(
    did_document: &Value,
    service_domain: &str,
    private_key: &PrivateKeyMaterial,
    version: &str,
) -> Result<String, AuthenticationError> {
    let parsed = generate_auth_payload(
        did_document,
        service_domain,
        private_key,
        version,
        None,
        None,
    )?;
    serde_json::to_string(&json!({
        "v": parsed.version,
        "did": parsed.did,
        "nonce": parsed.nonce,
        "timestamp": parsed.timestamp,
        "verification_method": parsed.verification_method,
        "signature": parsed.signature,
    }))
    .map_err(|_| AuthenticationError::JsonFailure)
}

pub(crate) fn generate_auth_header_with_overrides(
    did_document: &Value,
    service_domain: &str,
    private_key: &PrivateKeyMaterial,
    version: &str,
    nonce: Option<&str>,
    timestamp: Option<&str>,
) -> Result<String, AuthenticationError> {
    let parsed = generate_auth_payload(
        did_document,
        service_domain,
        private_key,
        version,
        nonce,
        timestamp,
    )?;
    Ok(format!(
        "DIDWba v=\"{}\", did=\"{}\", nonce=\"{}\", timestamp=\"{}\", verification_method=\"{}\", signature=\"{}\"",
        parsed.version,
        parsed.did,
        parsed.nonce,
        parsed.timestamp,
        parsed.verification_method,
        parsed.signature,
    ))
}

pub fn extract_auth_header_parts(
    auth_header: &str,
) -> Result<ParsedAuthHeader, AuthenticationError> {
    if !auth_header.trim_start().starts_with("DIDWba") {
        return Err(AuthenticationError::InvalidAuthorizationHeader);
    }

    let required_fields = [
        "did",
        "nonce",
        "timestamp",
        "verification_method",
        "signature",
    ];
    let mut values = BTreeMap::new();
    for field in required_fields {
        let pattern = format!(r#"(?i){field}=\"([^\"]+)\""#);
        let regex = Regex::new(&pattern).map_err(|_| AuthenticationError::RegexFailure)?;
        let capture = regex
            .captures(auth_header)
            .and_then(|caps| caps.get(1))
            .map(|matched| matched.as_str().to_string())
            .ok_or_else(|| {
                AuthenticationError::MissingAuthorizationField(field.to_string())
            })?;
        values.insert(field.to_string(), capture);
    }

    let version_regex = Regex::new(r#"(?i)v=\"([^\"]+)\""#)
        .map_err(|_| AuthenticationError::RegexFailure)?;
    let version = version_regex
        .captures(auth_header)
        .and_then(|caps| caps.get(1))
        .map(|matched| matched.as_str().to_string())
        .unwrap_or_else(|| "1.1".to_string());

    Ok(ParsedAuthHeader {
        did: values.remove("did").unwrap_or_default(),
        nonce: values.remove("nonce").unwrap_or_default(),
        timestamp: values.remove("timestamp").unwrap_or_default(),
        verification_method: values.remove("verification_method").unwrap_or_default(),
        signature: values.remove("signature").unwrap_or_default(),
        version,
    })
}

pub fn verify_auth_header_signature(
    auth_header: &str,
    did_document: &Value,
    service_domain: &str,
) -> Result<(), AuthenticationError> {
    let parsed = extract_auth_header_parts(auth_header)?;
    verify_auth_payload(&parsed, did_document, service_domain)
}

pub fn verify_auth_json_signature(
    auth_json: &str,
    did_document: &Value,
    service_domain: &str,
) -> Result<(), AuthenticationError> {
    let value: Value =
        serde_json::from_str(auth_json).map_err(|_| AuthenticationError::JsonFailure)?;
    let parsed = ParsedAuthHeader {
        did: value
            .get("did")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string(),
        nonce: value
            .get("nonce")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string(),
        timestamp: value
            .get("timestamp")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string(),
        verification_method: value
            .get("verification_method")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string(),
        signature: value
            .get("signature")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string(),
        version: value
            .get("v")
            .and_then(Value::as_str)
            .unwrap_or("1.1")
            .to_string(),
    };
    verify_auth_payload(&parsed, did_document, service_domain)
}

pub fn find_verification_method(
    did_document: &Value,
    verification_method_id: &str,
) -> Option<Value> {
    if let Some(methods) = did_document
        .get("verificationMethod")
        .and_then(Value::as_array)
    {
        for method in methods {
            if method.get("id").and_then(Value::as_str)
                == Some(verification_method_id)
            {
                return Some(method.clone());
            }
        }
    }
    if let Some(authentication) = did_document
        .get("authentication")
        .and_then(Value::as_array)
    {
        for method in authentication {
            if method.get("id").and_then(Value::as_str)
                == Some(verification_method_id)
            {
                return Some(method.clone());
            }
        }
    }
    None
}

pub fn is_authentication_authorized(
    did_document: &Value,
    verification_method_id: &str,
) -> bool {
    did_document
        .get("authentication")
        .and_then(Value::as_array)
        .map(|entries| {
            entries.iter().any(|entry| {
                entry.as_str() == Some(verification_method_id)
                    || entry.get("id").and_then(Value::as_str)
                        == Some(verification_method_id)
            })
        })
        .unwrap_or(false)
}

fn validate_e1_binding(did_document: &Value, expected_fingerprint: &str) -> bool {
    let proof = match did_document.get("proof") {
        Some(value) => value,
        None => return false,
    };
    if proof.get("type").and_then(Value::as_str)
        != Some(PROOF_TYPE_DATA_INTEGRITY)
    {
        return false;
    }
    if proof.get("cryptosuite").and_then(Value::as_str)
        != Some(CRYPTOSUITE_EDDSA_JCS_2022)
    {
        return false;
    }
    let verification_method = match proof.get("verificationMethod").and_then(Value::as_str) {
        Some(value) => value,
        None => return false,
    };
    let method = match find_verification_method(did_document, verification_method) {
        Some(value) => value,
        None => return false,
    };
    let public_key = match extract_public_key(&method) {
        Ok(value) => value,
        Err(_) => return false,
    };
    verify_w3c_proof(
        did_document,
        &public_key,
        ProofVerificationOptions {
            expected_purpose: Some("assertionMethod".to_string()),
            expected_domain: None,
            expected_challenge: None,
        },
    ) && compute_multikey_fingerprint(&public_key)
        .map(|value| value == expected_fingerprint)
        .unwrap_or(false)
}

fn validate_k1_binding(did_document: &Value, expected_fingerprint: &str) -> bool {
    let proof = match did_document.get("proof") {
        Some(value) => value,
        None => return false,
    };
    let verification_method = match proof.get("verificationMethod").and_then(Value::as_str) {
        Some(value) => value,
        None => return false,
    };
    let method = match find_verification_method(did_document, verification_method) {
        Some(value) => value,
        None => return false,
    };
    let public_key = match extract_public_key(&method) {
        Ok(value) => value,
        Err(_) => return false,
    };
    verify_w3c_proof(
        did_document,
        &public_key,
        ProofVerificationOptions {
            expected_purpose: Some("assertionMethod".to_string()),
            expected_domain: None,
            expected_challenge: None,
        },
    ) && compute_jwk_fingerprint(&public_key)
        .map(|value| value == expected_fingerprint)
        .unwrap_or(false)
}

fn generate_auth_payload(
    did_document: &Value,
    service_domain: &str,
    private_key: &PrivateKeyMaterial,
    version: &str,
    nonce_override: Option<&str>,
    timestamp_override: Option<&str>,
) -> Result<ParsedAuthHeader, AuthenticationError> {
    let did = did_document
        .get("id")
        .and_then(Value::as_str)
        .ok_or(AuthenticationError::InvalidDidDocument)?;
    let (method_dict, method_fragment) = select_authentication_method(did_document)?;
    let nonce = nonce_override
        .map(ToOwned::to_owned)
        .unwrap_or_else(|| base64url_encode(&rand::random::<[u8; 16]>()))
        ;
    let timestamp = timestamp_override
        .map(ToOwned::to_owned)
        .unwrap_or_else(|| Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string());
    let domain_field = domain_field_for_version(version);

    let payload = json!({
        "nonce": nonce,
        "timestamp": timestamp,
        domain_field: service_domain,
        "did": did,
    });
    let canonical = canonicalize_json(&payload)
        .map_err(|_| AuthenticationError::JsonFailure)?;
    let content_hash = Sha256::digest(canonical).to_vec();
    let signature_bytes = private_key
        .sign_message(&content_hash)
        .map_err(|_| AuthenticationError::SignatureGenerationFailed)?;
    let verifier = create_verification_method(&method_dict)
        .map_err(|err| AuthenticationError::VerificationMethod(err.to_string()))?;
    let signature = verifier
        .encode_signature(&signature_bytes)
        .map_err(|err| AuthenticationError::VerificationMethod(err.to_string()))?;

    Ok(ParsedAuthHeader {
        did: did.to_string(),
        nonce: payload
            .get("nonce")
            .and_then(Value::as_str)
            .unwrap_or_default()
            .to_string(),
        timestamp,
        verification_method: method_fragment,
        signature,
        version: version.to_string(),
    })
}

fn verify_auth_payload(
    parsed: &ParsedAuthHeader,
    did_document: &Value,
    service_domain: &str,
) -> Result<(), AuthenticationError> {
    if did_document
        .get("id")
        .and_then(Value::as_str)
        .map(|value| value.to_lowercase())
        != Some(parsed.did.to_lowercase())
    {
        return Err(AuthenticationError::VerificationFailed);
    }

    let domain_field = domain_field_for_version(&parsed.version);
    let payload = json!({
        "nonce": parsed.nonce,
        "timestamp": parsed.timestamp,
        domain_field: service_domain,
        "did": parsed.did,
    });
    let canonical = canonicalize_json(&payload)
        .map_err(|_| AuthenticationError::JsonFailure)?;
    let content_hash = Sha256::digest(canonical).to_vec();
    let verification_method_id = format!("{}#{}", parsed.did, parsed.verification_method);
    let method = find_verification_method(did_document, &verification_method_id)
        .ok_or(AuthenticationError::VerificationMethodNotFound)?;
    let verifier = create_verification_method(&method)
        .map_err(|err| AuthenticationError::VerificationMethod(err.to_string()))?;
    verifier
        .verify_signature(&content_hash, &parsed.signature)
        .map_err(|_| AuthenticationError::VerificationFailed)
}

fn select_authentication_method(
    did_document: &Value,
) -> Result<(Value, String), AuthenticationError> {
    let authentication = did_document
        .get("authentication")
        .and_then(Value::as_array)
        .ok_or(AuthenticationError::InvalidDidDocument)?;
    let first = authentication
        .first()
        .ok_or(AuthenticationError::InvalidDidDocument)?;
    if let Some(reference) = first.as_str() {
        let method = find_verification_method(did_document, reference)
            .ok_or(AuthenticationError::VerificationMethodNotFound)?;
        let fragment = reference
            .split('#')
            .last()
            .unwrap_or_default()
            .to_string();
        return Ok((method, fragment));
    }
    let id = first
        .get("id")
        .and_then(Value::as_str)
        .ok_or(AuthenticationError::InvalidDidDocument)?;
    Ok((
        first.clone(),
        id.split('#').last().unwrap_or_default().to_string(),
    ))
}

fn build_service_entries(
    did: &str,
    agent_description_url: Option<&str>,
    services: &[Value],
) -> Vec<Value> {
    let mut output = Vec::new();
    if let Some(url) = agent_description_url {
        output.push(json!({
            "id": format!("{did}#ad"),
            "type": "AgentDescription",
            "serviceEndpoint": url,
        }));
    }
    for service in services {
        let mut copy = service.clone();
        if let Some(object) = copy.as_object_mut() {
            if let Some(id_value) = object.get("id").and_then(Value::as_str) {
                if id_value.starts_with('#') {
                    object.insert(
                        "id".to_string(),
                        Value::String(format!("{did}{id_value}")),
                    );
                }
            }
        }
        output.push(copy);
    }
    output
}

fn build_did_base(hostname: &str, port: Option<u16>) -> String {
    match port {
        Some(value) => format!("did:wba:{hostname}%3A{value}"),
        None => format!("did:wba:{hostname}"),
    }
}

fn join_did(base: &str, path_segments: &[String]) -> String {
    if path_segments.is_empty() {
        base.to_string()
    } else {
        format!("{}:{}", base, path_segments.join(":"))
    }
}

fn jwk_thumbprint(jwk: &Value) -> Result<String, AuthenticationError> {
    let canonical = canonicalize_json(jwk)
        .map_err(|_| AuthenticationError::JsonFailure)?;
    Ok(base64url_encode(&Sha256::digest(canonical)))
}

fn secp256k1_public_key_to_jwk(
    key: &k256::ecdsa::VerifyingKey,
) -> Result<Value, AuthenticationError> {
    let point = key.to_encoded_point(false);
    let x = point.x().ok_or(AuthenticationError::InvalidDidDocument)?;
    let y = point.y().ok_or(AuthenticationError::InvalidDidDocument)?;
    Ok(json!({
        "crv": "secp256k1",
        "kty": "EC",
        "x": base64url_encode(x),
        "y": base64url_encode(y),
    }))
}

fn secp256r1_public_key_to_jwk(
    key: &p256::ecdsa::VerifyingKey,
) -> Result<Value, AuthenticationError> {
    let point = key.to_encoded_point(false);
    let x = point.x().ok_or(AuthenticationError::InvalidDidDocument)?;
    let y = point.y().ok_or(AuthenticationError::InvalidDidDocument)?;
    Ok(json!({
        "crv": "P-256",
        "kty": "EC",
        "x": base64url_encode(x),
        "y": base64url_encode(y),
    }))
}

fn ed25519_public_key_to_multibase(key: &ed25519_dalek::VerifyingKey) -> String {
    let mut bytes = vec![0xed, 0x01];
    bytes.extend_from_slice(&key.to_bytes());
    format!("z{}", bs58::encode(bytes).into_string())
}

fn x25519_public_key_to_multibase(bytes: &[u8; 32]) -> String {
    let mut prefixed = vec![0xec, 0x01];
    prefixed.extend_from_slice(bytes);
    format!("z{}", bs58::encode(prefixed).into_string())
}

fn domain_field_for_version(version: &str) -> &'static str {
    version
        .parse::<f64>()
        .map(|value| if value >= 1.1 { "aud" } else { "service" })
        .unwrap_or("service")
}

fn is_ip_address(hostname: &str) -> bool {
    hostname.parse::<std::net::IpAddr>().is_ok()
}
