use std::collections::{BTreeMap, HashMap};
use std::sync::Arc;

use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use super::did_wba::{
    extract_auth_header_parts, is_authentication_authorized,
    resolve_did_wba_document_with_options, validate_did_document_binding,
    verify_auth_header_signature, DidResolutionOptions,
};
use super::http_signatures::{
    extract_signature_metadata, verify_http_message_signature,
};

#[derive(Clone)]
pub struct DidWbaVerifierConfig {
    pub jwt_private_key: Option<String>,
    pub jwt_public_key: Option<String>,
    pub jwt_algorithm: String,
    pub access_token_expire_minutes: i64,
    pub nonce_expiration_minutes: i64,
    pub timestamp_expiration_minutes: i64,
    pub allowed_domains: Option<Vec<String>>,
    pub allow_http_signatures: bool,
    pub allow_legacy_didwba: bool,
    pub emit_authentication_info_header: bool,
    pub emit_legacy_authorization_header: bool,
    pub require_nonce_for_http_signatures: bool,
    pub did_resolution_options: DidResolutionOptions,
    pub external_nonce_validator: Option<Arc<dyn Fn(&str, &str) -> bool + Send + Sync>>,
}

impl Default for DidWbaVerifierConfig {
    fn default() -> Self {
        Self {
            jwt_private_key: None,
            jwt_public_key: None,
            jwt_algorithm: "RS256".to_string(),
            access_token_expire_minutes: 60,
            nonce_expiration_minutes: 6,
            timestamp_expiration_minutes: 5,
            allowed_domains: None,
            allow_http_signatures: true,
            allow_legacy_didwba: true,
            emit_authentication_info_header: true,
            emit_legacy_authorization_header: true,
            require_nonce_for_http_signatures: true,
            did_resolution_options: DidResolutionOptions::default(),
            external_nonce_validator: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationSuccess {
    pub did: String,
    pub auth_scheme: String,
    pub response_headers: BTreeMap<String, String>,
    pub access_token: Option<String>,
    pub token_type: Option<String>,
}

#[derive(Debug, Error)]
#[error("{message}")]
pub struct DidWbaVerifierError {
    pub message: String,
    pub status_code: u16,
    pub headers: BTreeMap<String, String>,
}

pub struct DidWbaVerifier {
    config: DidWbaVerifierConfig,
    used_nonces: HashMap<String, chrono::DateTime<Utc>>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,
    iat: i64,
    exp: i64,
}

impl DidWbaVerifier {
    pub fn new(config: DidWbaVerifierConfig) -> Self {
        Self {
            config,
            used_nonces: HashMap::new(),
        }
    }

    pub async fn verify_request(
        &mut self,
        method: &str,
        url: &str,
        headers: &BTreeMap<String, String>,
        body: Option<&[u8]>,
        domain: Option<&str>,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let request_domain = domain
            .map(|value| value.to_string())
            .unwrap_or_else(|| extract_domain(url));
        self.validate_allowed_domain(&request_domain)?;

        if let Some(auth_header) = get_header_case_insensitive(headers, "Authorization") {
            if auth_header.starts_with("Bearer ") {
                return self.handle_bearer_auth(auth_header);
            }
        }

        if get_header_case_insensitive(headers, "Signature-Input").is_some()
            || get_header_case_insensitive(headers, "Signature").is_some()
        {
            if !self.config.allow_http_signatures {
                return Err(self.challenge_error(
                    "HTTP Message Signatures authentication is disabled",
                    401,
                    &request_domain,
                    "invalid_request",
                ));
            }
            return self
                .handle_http_signature_auth(method, url, headers, body, &request_domain)
                .await;
        }

        if let Some(auth_header) = get_header_case_insensitive(headers, "Authorization") {
            if !self.config.allow_legacy_didwba {
                return Err(self.challenge_error(
                    "Legacy DIDWba authentication is disabled",
                    401,
                    &request_domain,
                    "invalid_request",
                ));
            }
            return self
                .handle_legacy_did_auth(auth_header, &request_domain)
                .await;
        }

        Err(DidWbaVerifierError {
            message: "Missing authentication headers".to_string(),
            status_code: 401,
            headers: BTreeMap::new(),
        })
    }


    pub async fn verify_request_with_did_document(
        &mut self,
        method: &str,
        url: &str,
        headers: &BTreeMap<String, String>,
        body: Option<&[u8]>,
        domain: Option<&str>,
        did_document: &serde_json::Value,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let request_domain = domain
            .map(|value| value.to_string())
            .unwrap_or_else(|| extract_domain(url));
        self.validate_allowed_domain(&request_domain)?;

        if let Some(auth_header) = get_header_case_insensitive(headers, "Authorization") {
            if auth_header.starts_with("Bearer ") {
                return self.handle_bearer_auth(auth_header);
            }
        }

        if get_header_case_insensitive(headers, "Signature-Input").is_some()
            || get_header_case_insensitive(headers, "Signature").is_some()
        {
            if !self.config.allow_http_signatures {
                return Err(self.challenge_error(
                    "HTTP Message Signatures authentication is disabled",
                    401,
                    &request_domain,
                    "invalid_request",
                ));
            }
            return self.handle_http_signature_auth_with_document(
                method,
                url,
                headers,
                body,
                &request_domain,
                did_document,
            );
        }

        if let Some(auth_header) = get_header_case_insensitive(headers, "Authorization") {
            if !self.config.allow_legacy_didwba {
                return Err(self.challenge_error(
                    "Legacy DIDWba authentication is disabled",
                    401,
                    &request_domain,
                    "invalid_request",
                ));
            }
            return self.handle_legacy_did_auth_with_document(
                auth_header,
                &request_domain,
                did_document,
            );
        }

        Err(DidWbaVerifierError {
            message: "Missing authentication headers".to_string(),
            status_code: 401,
            headers: BTreeMap::new(),
        })
    }

    async fn handle_http_signature_auth(
        &mut self,
        method: &str,
        url: &str,
        headers: &BTreeMap<String, String>,
        body: Option<&[u8]>,
        domain: &str,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let metadata = extract_signature_metadata(headers).map_err(|_| {
            self.challenge_error(
                "Invalid signature metadata",
                401,
                domain,
                "invalid_request",
            )
        })?;
        let did = metadata
            .keyid
            .split('#')
            .next()
            .unwrap_or_default()
            .to_string();
        let did_document = resolve_did_wba_document_with_options(
            &did,
            false,
            &self.config.did_resolution_options,
        )
        .await
        .map_err(|_| self.challenge_error("Failed to resolve DID document", 401, domain, "invalid_did"))?;
        self.handle_http_signature_auth_with_document(method, url, headers, body, domain, &did_document)
    }

    fn handle_http_signature_auth_with_document(
        &mut self,
        method: &str,
        url: &str,
        headers: &BTreeMap<String, String>,
        body: Option<&[u8]>,
        domain: &str,
        did_document: &serde_json::Value,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let metadata = extract_signature_metadata(headers).map_err(|_| {
            self.challenge_error(
                "Invalid signature metadata",
                401,
                domain,
                "invalid_request",
            )
        })?;
        let did = metadata
            .keyid
            .split('#')
            .next()
            .unwrap_or_default()
            .to_string();

        self.validate_did_binding(did_document)?;
        if !is_authentication_authorized(did_document, &metadata.keyid) {
            return Err(DidWbaVerifierError {
                message: "Verification method is not authorized for authentication".to_string(),
                status_code: 403,
                headers: BTreeMap::new(),
            });
        }
        let verification = verify_http_message_signature(
            did_document,
            method,
            url,
            headers,
            body,
        )
        .map_err(|_| self.challenge_error("Invalid signature", 401, domain, "invalid_signature"))?;
        if !self.verify_http_signature_time_window(verification.created, verification.expires) {
            return Err(self.challenge_error(
                "HTTP signature timestamp is expired or invalid",
                401,
                domain,
                "invalid_timestamp",
            ));
        }
        if self.config.require_nonce_for_http_signatures && verification.nonce.is_none() {
            return Err(self.challenge_error(
                "HTTP signature nonce is required",
                401,
                domain,
                "invalid_nonce",
            ));
        }
        if let Some(nonce) = verification.nonce.as_deref() {
            if !self.is_valid_nonce(&did, nonce) {
                return Err(self.challenge_error(
                    "Nonce has already been used or expired",
                    401,
                    domain,
                    "invalid_nonce",
                ));
            }
        }
        let access_token = self.create_access_token(&did)?;
        Ok(self.build_success_result(&did, "http_signatures", Some(access_token)))
    }

    async fn handle_legacy_did_auth(
        &mut self,
        authorization: &str,
        domain: &str,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let parsed = extract_auth_header_parts(authorization).map_err(|_| {
            self.challenge_error(
                "Invalid authorization header format",
                401,
                domain,
                "invalid_request",
            )
        })?;
        let did_document = resolve_did_wba_document_with_options(
            &parsed.did,
            false,
            &self.config.did_resolution_options,
        )
        .await
        .map_err(|_| self.challenge_error("Failed to resolve DID document", 401, domain, "invalid_did"))?;
        self.handle_legacy_did_auth_with_document(authorization, domain, &did_document)
    }

    fn handle_legacy_did_auth_with_document(
        &mut self,
        authorization: &str,
        domain: &str,
        did_document: &serde_json::Value,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let parsed = extract_auth_header_parts(authorization).map_err(|_| {
            self.challenge_error(
                "Invalid authorization header format",
                401,
                domain,
                "invalid_request",
            )
        })?;
        if !self.verify_legacy_timestamp(&parsed.timestamp) {
            return Err(self.challenge_error(
                "Legacy DIDWba timestamp is expired or invalid",
                401,
                domain,
                "invalid_timestamp",
            ));
        }
        if !self.is_valid_nonce(&parsed.did, &parsed.nonce) {
            return Err(self.challenge_error(
                "Legacy DIDWba nonce has already been used or expired",
                401,
                domain,
                "invalid_nonce",
            ));
        }
        self.validate_did_binding(did_document)?;
        let keyid = format!("{}#{}", parsed.did, parsed.verification_method);
        if !is_authentication_authorized(did_document, &keyid) {
            return Err(DidWbaVerifierError {
                message: "Verification method is not authorized for authentication".to_string(),
                status_code: 403,
                headers: BTreeMap::new(),
            });
        }
        verify_auth_header_signature(authorization, did_document, domain).map_err(|_| {
            self.challenge_error(
                "Legacy DIDWba signature verification failed",
                401,
                domain,
                "invalid_signature",
            )
        })?;
        let access_token = self.create_access_token(&parsed.did)?;
        Ok(self.build_success_result(&parsed.did, "legacy_didwba", Some(access_token)))
    }

    fn handle_bearer_auth(
        &self,
        token_header_value: &str,
    ) -> Result<VerificationSuccess, DidWbaVerifierError> {
        let token = token_header_value.trim_start_matches("Bearer ");
        let decoding_key = self.decoding_key()?;
        let algorithm = parse_algorithm(&self.config.jwt_algorithm)?;
        let mut validation = Validation::new(algorithm);
        validation.validate_exp = true;
        let token_data = decode::<Claims>(token, &decoding_key, &validation)
            .map_err(|_| DidWbaVerifierError {
                message: "Invalid token".to_string(),
                status_code: 401,
                headers: BTreeMap::new(),
            })?;
        let claims = token_data.claims;
        let now = Utc::now().timestamp();
        if claims.iat > now + 5 {
            return Err(DidWbaVerifierError {
                message: "Token issued in the future".to_string(),
                status_code: 401,
                headers: BTreeMap::new(),
            });
        }
        if claims.exp <= now - 5 {
            return Err(DidWbaVerifierError {
                message: "Token has expired".to_string(),
                status_code: 401,
                headers: BTreeMap::new(),
            });
        }
        Ok(self.build_success_result(&claims.sub, "bearer", None))
    }

    fn build_success_result(
        &self,
        did: &str,
        auth_scheme: &str,
        access_token: Option<String>,
    ) -> VerificationSuccess {
        let mut response_headers = BTreeMap::new();
        if let Some(token) = access_token.as_ref() {
            let expires_in = self.config.access_token_expire_minutes * 60;
            if self.config.emit_authentication_info_header {
                response_headers.insert(
                    "Authentication-Info".to_string(),
                    format!(
                        "access_token=\"{}\", token_type=\"Bearer\", expires_in={}",
                        token, expires_in
                    ),
                );
            }
            if self.config.emit_legacy_authorization_header {
                response_headers.insert(
                    "Authorization".to_string(),
                    format!("Bearer {}", token),
                );
            }
        }
        VerificationSuccess {
            did: did.to_string(),
            auth_scheme: auth_scheme.to_string(),
            response_headers,
            access_token,
            token_type: Some("bearer".to_string()),
        }
    }

    fn validate_did_binding(
        &self,
        did_document: &serde_json::Value,
    ) -> Result<(), DidWbaVerifierError> {
        if validate_did_document_binding(did_document, false) {
            Ok(())
        } else {
            Err(DidWbaVerifierError {
                message: "DID binding verification failed".to_string(),
                status_code: 401,
                headers: BTreeMap::new(),
            })
        }
    }

    fn verify_legacy_timestamp(&self, timestamp: &str) -> bool {
        let parsed = chrono::DateTime::parse_from_rfc3339(&timestamp.replace('Z', "+00:00"));
        if let Ok(value) = parsed {
            let current = Utc::now();
            let request = value.with_timezone(&Utc);
            if request > current + Duration::minutes(1) {
                return false;
            }
            return current - request <= Duration::minutes(self.config.timestamp_expiration_minutes);
        }
        false
    }

    fn verify_http_signature_time_window(&self, created: i64, expires: Option<i64>) -> bool {
        let current = Utc::now().timestamp();
        if created > current + 60 {
            return false;
        }
        if current - created > self.config.timestamp_expiration_minutes * 60 {
            return false;
        }
        if let Some(value) = expires {
            if value < current {
                return false;
            }
        }
        true
    }

    fn is_valid_nonce(&mut self, did: &str, nonce: &str) -> bool {
        if let Some(validator) = self.config.external_nonce_validator.as_ref() {
            return validator(did, nonce);
        }
        let current = Utc::now();
        self.used_nonces.retain(|_, issued_at| {
            current.signed_duration_since(*issued_at)
                <= Duration::minutes(self.config.nonce_expiration_minutes)
        });
        let cache_key = format!("{}:{}", did, nonce);
        if self.used_nonces.contains_key(&cache_key) {
            return false;
        }
        self.used_nonces.insert(cache_key, current);
        true
    }

    fn create_access_token(&self, did: &str) -> Result<String, DidWbaVerifierError> {
        let now = Utc::now().timestamp();
        let claims = Claims {
            sub: did.to_string(),
            iat: now,
            exp: now + self.config.access_token_expire_minutes * 60,
        };
        let algorithm = parse_algorithm(&self.config.jwt_algorithm)?;
        let key = self.encoding_key()?;
        let mut header = Header::new(algorithm);
        header.typ = Some("JWT".to_string());
        encode(&header, &claims, &key).map_err(|_| DidWbaVerifierError {
            message: "Internal server error during token generation".to_string(),
            status_code: 500,
            headers: BTreeMap::new(),
        })
    }

    fn encoding_key(&self) -> Result<EncodingKey, DidWbaVerifierError> {
        let secret = self.config.jwt_private_key.as_ref().ok_or(DidWbaVerifierError {
            message: "Missing JWT private key".to_string(),
            status_code: 500,
            headers: BTreeMap::new(),
        })?;
        match self.config.jwt_algorithm.as_str() {
            "HS256" | "HS384" | "HS512" => Ok(EncodingKey::from_secret(secret.as_bytes())),
            _ => EncodingKey::from_rsa_pem(secret.as_bytes()).map_err(|_| DidWbaVerifierError {
                message: "Invalid JWT private key".to_string(),
                status_code: 500,
                headers: BTreeMap::new(),
            }),
        }
    }

    fn decoding_key(&self) -> Result<DecodingKey, DidWbaVerifierError> {
        let secret = self.config.jwt_public_key.as_ref().or(self.config.jwt_private_key.as_ref()).ok_or(DidWbaVerifierError {
            message: "Missing JWT public key".to_string(),
            status_code: 500,
            headers: BTreeMap::new(),
        })?;
        match self.config.jwt_algorithm.as_str() {
            "HS256" | "HS384" | "HS512" => Ok(DecodingKey::from_secret(secret.as_bytes())),
            _ => DecodingKey::from_rsa_pem(secret.as_bytes()).map_err(|_| DidWbaVerifierError {
                message: "Invalid JWT public key".to_string(),
                status_code: 500,
                headers: BTreeMap::new(),
            }),
        }
    }

    fn validate_allowed_domain(&self, domain: &str) -> Result<(), DidWbaVerifierError> {
        if let Some(allowed) = self.config.allowed_domains.as_ref() {
            if !allowed.iter().any(|item| item == domain) {
                return Err(DidWbaVerifierError {
                    message: "Domain is not allowed".to_string(),
                    status_code: 403,
                    headers: BTreeMap::new(),
                });
            }
        }
        Ok(())
    }

    fn challenge_error(
        &self,
        message: &str,
        status_code: u16,
        domain: &str,
        error: &str,
    ) -> DidWbaVerifierError {
        let mut headers = BTreeMap::new();
        headers.insert(
            "WWW-Authenticate".to_string(),
            format!(
                "DIDWba realm=\"{}\", error=\"{}\", error_description=\"{}\"",
                domain, error, message
            ),
        );
        if self.config.allow_http_signatures {
            headers.insert(
                "Accept-Signature".to_string(),
                "sig1=(\"@method\" \"@target-uri\" \"@authority\" \"content-digest\");created;expires;nonce;keyid".to_string(),
            );
        }
        DidWbaVerifierError {
            message: message.to_string(),
            status_code,
            headers,
        }
    }
}

fn parse_algorithm(value: &str) -> Result<Algorithm, DidWbaVerifierError> {
    match value {
        "HS256" => Ok(Algorithm::HS256),
        "HS384" => Ok(Algorithm::HS384),
        "HS512" => Ok(Algorithm::HS512),
        "RS256" => Ok(Algorithm::RS256),
        "RS384" => Ok(Algorithm::RS384),
        "RS512" => Ok(Algorithm::RS512),
        _ => Err(DidWbaVerifierError {
            message: "Unsupported JWT algorithm".to_string(),
            status_code: 500,
            headers: BTreeMap::new(),
        }),
    }
}

fn get_header_case_insensitive<'a>(
    headers: &'a BTreeMap<String, String>,
    name: &str,
) -> Option<&'a String> {
    headers
        .iter()
        .find(|(key, _)| key.eq_ignore_ascii_case(name))
        .map(|(_, value)| value)
}

fn extract_domain(url: &str) -> String {
    url::Url::parse(url)
        .ok()
        .and_then(|value| value.host_str().map(|item| item.to_string()))
        .unwrap_or_else(|| url.to_string())
}
