// Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.

//! Framework-level token verification for PSK registration.
//!
//! Verifies attestation tokens using the RA Agent's public key.
//! RA public key is mandatory — the agent will fail to start without it.

use std::fs;
use std::sync::OnceLock;

use thiserror::Error;

#[derive(Debug, Error)]
pub enum TokenVerifierError {
    #[error("RA public key not initialized")]
    NotInitialized,
    #[error("failed to read RA public key from {path}: {source}")]
    KeyReadFailed {
        path: String,
        source: std::io::Error,
    },
    #[error("token verification failed: {0}")]
    VerificationFailed(String),
    #[error("token signature verification failed")]
    SignatureInvalid,
    #[error("token body is empty")]
    EmptyBody,
    #[error("token verification status is not pass")]
    StatusNotPass,
    #[error("base64 decode failed: {0}")]
    Base64Error(String),
    #[error("utf8 decode failed: {0}")]
    Utf8Error(String),
    #[error("json parse failed: {0}")]
    JsonError(String),
}

static RA_PUBLIC_KEY_PEM: OnceLock<String> = OnceLock::new();

pub fn init_token_verifier() -> Result<(), TokenVerifierError> {
    let path = crate::config_manager::get_agent_config()
        .map(|c| c.ra_public_key_path.clone())
        .unwrap_or_default();
    if path.is_empty() {
        return Err(TokenVerifierError::NotInitialized);
    }
    let pem = fs::read_to_string(&path).map_err(|e| TokenVerifierError::KeyReadFailed {
        path: path.clone(),
        source: e,
    })?;
    let _ = RA_PUBLIC_KEY_PEM.set(pem);
    Ok(())
}

pub fn verify_token(token: &str) -> Result<serde_json::Value, TokenVerifierError> {
    let ra_pub_key = RA_PUBLIC_KEY_PEM
        .get()
        .ok_or_else(|| {
            log::error!("token verification failed: RA public key not initialized");
            TokenVerifierError::NotInitialized
        })?;
    verify_token_with_key(token, ra_pub_key)
}

fn verify_token_with_key(
    token: &str,
    ra_pub_key: &str,
) -> Result<serde_json::Value, TokenVerifierError> {
    let result =
        token_verifier::manager::TokenManager::verify_token_with_pub_key(token, ra_pub_key)
            .map_err(|e| {
                log::error!("token verification failed: {}", e);
                TokenVerifierError::VerificationFailed(e.to_string())
            })?;

    if !result.verify_result {
        log::error!("token signature verification failed");
        return Err(TokenVerifierError::SignatureInvalid);
    }
    let token_body = result.token_body.ok_or_else(|| {
        log::error!("token verification failed: token body is empty");
        TokenVerifierError::EmptyBody
    })?;

    match token_body.get("status").and_then(|v| v.as_str()) {
        Some(s) if s == "pass" => Ok(token_body),
        _ => {
            log::error!("token verification failed: status is not 'pass'");
            Err(TokenVerifierError::StatusNotPass)
        }
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    // Test: verify verify_token returns NotInitialized when RA public key has not been set
    #[test]
    fn test_verify_token_not_initialized() {
        // RA_PUBLIC_KEY_PEM is a OnceLock that may or may not be set depending on
        // test execution order. If already set from a prior test, we get a
        // VerificationFailed from the token_verifier crate instead.
        let result = verify_token("any_token");
        assert!(result.is_err());
        match result.unwrap_err() {
            TokenVerifierError::NotInitialized | TokenVerifierError::VerificationFailed(_) => {}
            other => panic!("unexpected error: {:?}", other),
        }
    }

    // Test: verify init_token_verifier returns error when config is not initialized
    #[test]
    fn test_init_token_verifier_no_config() {
        // get_agent_config() returns None because init_config_center() was never called
        let result = init_token_verifier();
        assert!(result.is_err());
        match result.unwrap_err() {
            TokenVerifierError::NotInitialized | TokenVerifierError::KeyReadFailed { .. } => {}
            other => panic!("unexpected error: {:?}", other),
        }
    }

    // Test: verify TokenVerifierError display messages contain expected substrings
    #[test]
    fn test_error_display_messages() {
        assert!(format!("{}", TokenVerifierError::NotInitialized).contains("not initialized"));
        assert!(format!("{}", TokenVerifierError::SignatureInvalid).contains("signature"));
        assert!(format!("{}", TokenVerifierError::EmptyBody).contains("empty"));
        assert!(format!("{}", TokenVerifierError::StatusNotPass).contains("not pass"));
    }

    // Test: verify TokenVerifierError Base64Error and Utf8Error carry message
    #[test]
    fn test_wrapped_error_messages() {
        let err = TokenVerifierError::Base64Error("bad b64".to_string());
        assert!(format!("{}", err).contains("bad b64"));
        let err = TokenVerifierError::Utf8Error("bad utf8".to_string());
        assert!(format!("{}", err).contains("bad utf8"));
        let err = TokenVerifierError::JsonError("bad json".to_string());
        assert!(format!("{}", err).contains("bad json"));
    }

    // Test: verify TokenVerifierError derives Debug
    #[test]
    fn test_error_debug_format() {
        let err = TokenVerifierError::NotInitialized;
        let debug_str = format!("{:?}", err);
        assert!(debug_str.contains("NotInitialized"));
    }

    // Test: verify TokenVerifierError KeyReadFailed displays path and source
    #[test]
    fn test_key_read_failed_display() {
        let err = TokenVerifierError::KeyReadFailed {
            path: "/etc/kdc/ra_key.pem".to_string(),
            source: std::io::Error::new(std::io::ErrorKind::NotFound, "No such file"),
        };
        let msg = format!("{}", err);
        assert!(msg.contains("/etc/kdc/ra_key.pem"));
        assert!(msg.contains("No such file"));
    }

    // Test: verify TokenVerifierError VerificationFailed displays message
    #[test]
    fn test_verification_failed_display() {
        let err = TokenVerifierError::VerificationFailed("bad signature".to_string());
        let msg = format!("{}", err);
        assert!(msg.contains("bad signature"));
    }

    // Test: verify verify_token with RA key set but invalid token returns error
    #[test]
    fn test_verify_token_with_invalid_token() {
        // If RA_PUBLIC_KEY_PEM was set by prior test, try to verify an invalid token
        // This exercises the verify_token_with_key path
        let result = verify_token("eyJhbGciOiJFUzI1NiJ9.invalid.signature");
        // Will fail either because RA key isn't set (NotInitialized) or because
        // token verification fails (VerificationFailed/SignatureInvalid/etc.)
        assert!(result.is_err());
    }

    // Test: verify all TokenVerifierError variants have proper Debug representation
    #[test]
    fn test_all_error_variants_debug() {
        assert!(format!("{:?}", TokenVerifierError::NotInitialized).contains("NotInitialized"));
        assert!(format!("{:?}", TokenVerifierError::SignatureInvalid).contains("SignatureInvalid"));
        assert!(format!("{:?}", TokenVerifierError::EmptyBody).contains("EmptyBody"));
        assert!(format!("{:?}", TokenVerifierError::StatusNotPass).contains("StatusNotPass"));
        assert!(format!("{:?}", TokenVerifierError::Base64Error("b64".to_string())).contains("Base64Error"));
        assert!(format!("{:?}", TokenVerifierError::Utf8Error("utf8".to_string())).contains("Utf8Error"));
        assert!(format!("{:?}", TokenVerifierError::JsonError("json".to_string())).contains("JsonError"));
        assert!(format!("{:?}", TokenVerifierError::VerificationFailed("vf".to_string())).contains("VerificationFailed"));
    }

    // Test: verify KeyReadFailed variant preserves error context
    #[test]
    fn test_key_read_failed_preserves_context() {
        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
        let err = TokenVerifierError::KeyReadFailed {
            path: "/protected/key.pem".to_string(),
            source: io_err,
        };
        let debug = format!("{:?}", err);
        assert!(debug.contains("KeyReadFailed"));
    }

    // Test: verify verify_token_with_key rejects malformed token with signature invalid
    #[test]
    fn test_verify_token_with_key_malformed() {
        let ra_pub_key = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinvalid\n-----END PUBLIC KEY-----";
        let result = verify_token_with_key("not-a-valid-token", ra_pub_key);
        assert!(result.is_err());
    }

    // Test: verify verify_token_with_key rejects empty token
    #[test]
    fn test_verify_token_with_key_empty() {
        let ra_pub_key = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEinvalid\n-----END PUBLIC KEY-----";
        let result = verify_token_with_key("", ra_pub_key);
        assert!(result.is_err());
    }

    #[test]
    fn test_init_token_verifier_empty_ra_key_path() {
        fn mock_get_agent_config() -> Option<&'static crate::config_manager::ConfigCenter> {
            None
        }
        let _mock = mockrs::mock!(
            crate::config_manager::get_agent_config,
            mock_get_agent_config
        );
        let result = init_token_verifier();
        assert!(result.is_err());
        match result.unwrap_err() {
            TokenVerifierError::NotInitialized => {}
            other => panic!("expected NotInitialized, got: {:?}", other),
        }
    }

    #[test]
    fn test_init_token_verifier_invalid_key_path() {
        fn mock_get_agent_config() -> Option<&'static crate::config_manager::ConfigCenter> {
            // get_agent_config returns Option<&'static ConfigCenter>, we can't easily
            // construct a static reference. Return None to trigger empty path branch.
            None
        }
        let _mock = mockrs::mock!(
            crate::config_manager::get_agent_config,
            mock_get_agent_config
        );
        let result = init_token_verifier();
        assert!(result.is_err());
    }

    #[test]
    fn test_verify_token_with_key_valid_key_invalid_jwt() {
        // Generate a valid P-256 key PEM
        let group =
            openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1).unwrap();
        let ec_key = openssl::ec::EcKey::generate(&group).unwrap();
        let pkey = openssl::pkey::PKey::from_ec_key(ec_key).unwrap();
        let pub_pem = String::from_utf8(pkey.public_key_to_pem().unwrap()).unwrap();

        // Use a well-formed-looking but forged JWT
        let fake_jwt = "eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.fake_signature_here";
        let result = verify_token_with_key(fake_jwt, &pub_pem);
        assert!(result.is_err());
    }

    #[test]
    fn test_verify_token_with_key_garbage_key() {
        let result = verify_token_with_key("some.token.here", "not-a-valid-key");
        assert!(result.is_err());
    }

    #[test]
    fn test_init_token_verifier_key_read_failed() {
        fn mock_get_agent_config() -> Option<&'static crate::config_manager::ConfigCenter> {
            use crate::types::KdcLogLevel;
            let config = crate::config_manager::ConfigCenter {
                ip: "127.0.0.1".to_string(),
                port: 8080,
                ca_path: "/nonexistent".to_string(),
                cert_path: "/nonexistent".to_string(),
                private_path: "/nonexistent".to_string(),
                crl_path: String::new(),
                ra_public_key_path: "/nonexistent/ra_key.pem".to_string(),
                log_path: "/tmp/kdc_test.log".to_string(),
                data_path: "/tmp".to_string(),
                plugins: vec![],
                log_max_size: 10,
                log_backup_count: 5,
                min_log_level: KdcLogLevel::Info,
            };
            Some(Box::leak(Box::new(config)))
        }
        let _mock = mockrs::mock!(
            crate::config_manager::get_agent_config,
            mock_get_agent_config
        );
        let result = init_token_verifier();
        assert!(result.is_err());
        match result.unwrap_err() {
            TokenVerifierError::KeyReadFailed { path, .. } => {
                assert!(path.contains("/nonexistent/ra_key.pem"));
            }
            other => panic!("expected KeyReadFailed, got: {:?}", other),
        }
    }

    #[test]
    fn test_verify_token_with_key_empty_body_path() {
        fn mock_verify(
            _token: &str,
            _pub_key: &str,
        ) -> Result<
            token_verifier::entity::SimpleVerifyResult,
            token_verifier::error::TokenVerificationError,
        > {
            Ok(token_verifier::entity::SimpleVerifyResult {
                verify_result: true,
                token_body: None,
                token_header: None,
            })
        }
        let _mock = mockrs::mock!(
            token_verifier::manager::TokenManager::verify_token_with_pub_key,
            mock_verify
        );
        let result = verify_token_with_key("some.token", "any_key");
        assert!(result.is_err());
        match result.unwrap_err() {
            TokenVerifierError::EmptyBody => {}
            other => panic!("expected EmptyBody, got: {:?}", other),
        }
    }

    #[test]
    fn test_verify_token_with_key_status_not_pass() {
        fn mock_verify(
            _token: &str,
            _pub_key: &str,
        ) -> Result<
            token_verifier::entity::SimpleVerifyResult,
            token_verifier::error::TokenVerificationError,
        > {
            Ok(token_verifier::entity::SimpleVerifyResult {
                verify_result: true,
                token_body: Some(serde_json::json!({"status": "fail"})),
                token_header: None,
            })
        }
        let _mock = mockrs::mock!(
            token_verifier::manager::TokenManager::verify_token_with_pub_key,
            mock_verify
        );
        let result = verify_token_with_key("some.token", "any_key");
        assert!(result.is_err());
        match result.unwrap_err() {
            TokenVerifierError::StatusNotPass => {}
            other => panic!("expected StatusNotPass, got: {:?}", other),
        }
    }

    #[test]
    fn test_verify_token_with_key_success_path() {
        fn mock_verify(
            _token: &str,
            _pub_key: &str,
        ) -> Result<
            token_verifier::entity::SimpleVerifyResult,
            token_verifier::error::TokenVerificationError,
        > {
            Ok(token_verifier::entity::SimpleVerifyResult {
                verify_result: true,
                token_body: Some(serde_json::json!({"status": "pass", "data": "test"})),
                token_header: None,
            })
        }
        let _mock = mockrs::mock!(
            token_verifier::manager::TokenManager::verify_token_with_pub_key,
            mock_verify
        );
        let result = verify_token_with_key("some.token", "any_key");
        assert!(result.is_ok());
        let body = result.unwrap();
        assert_eq!(body["status"], "pass");
    }
}