use crate::auth::{AuthError, AuthResult};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{
    window, AuthenticatorAttestationResponse, AuthenticatorAssertionResponse,
    CredentialsContainer, PublicKeyCredential, PublicKeyCredentialCreationOptions,
    PublicKeyCredentialRequestOptions,
};
use base64::{Engine as _, engine::general_purpose};
use js_sys::{Array, Object, Uint8Array};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnRegistrationOptions {
    pub challenge: String,
    pub rp: RelyingParty,
    pub user: WebAuthnUser,
    pub pub_key_cred_params: Vec<PubKeyCredParam>,
    pub timeout: Option<u32>,
    pub exclude_credentials: Option<Vec<CredentialDescriptor>>,
    pub authenticator_selection: Option<AuthenticatorSelection>,
    pub attestation: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnAuthenticationOptions {
    pub challenge: String,
    pub timeout: Option<u32>,
    pub rp_id: Option<String>,
    pub allow_credentials: Option<Vec<CredentialDescriptor>>,
    pub user_verification: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelyingParty {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnUser {
    pub id: String,
    pub name: String,
    pub display_name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PubKeyCredParam {
    pub type_: String,
    pub alg: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CredentialDescriptor {
    pub type_: String,
    pub id: String,
    pub transports: Option<Vec<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthenticatorSelection {
    pub authenticator_attachment: Option<String>,
    pub resident_key: Option<String>,
    pub require_resident_key: Option<bool>,
    pub user_verification: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnRegistrationResult {
    pub id: String,
    pub raw_id: String,
    pub type_: String,
    pub response: AttestationResponse,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebAuthnAuthenticationResult {
    pub id: String,
    pub raw_id: String,
    pub type_: String,
    pub response: AssertionResponse,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttestationResponse {
    pub client_data_json: String,
    pub attestation_object: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssertionResponse {
    pub client_data_json: String,
    pub authenticator_data: String,
    pub signature: String,
    pub user_handle: Option<String>,
}

pub struct WebAuthnManager;

impl WebAuthnManager {
    pub fn new() -> Self {
        Self
    }

    pub fn is_supported(&self) -> bool {
        window()
            .and_then(|w| w.navigator().credentials().ok())
            .and_then(|c| js_sys::Reflect::has(&c, &"create".into()).ok())
            .unwrap_or(false)
    }

    pub async fn register_credential(
        &self,
        options: WebAuthnRegistrationOptions,
    ) -> AuthResult<WebAuthnRegistrationResult> {
        if !self.is_supported() {
            return Err(AuthError {
                message: "WebAuthn is not supported in this browser".to_string(),
                code: Some("WEBAUTHN_NOT_SUPPORTED".to_string()),
                field: None,
            });
        }

        let window = window().ok_or_else(|| AuthError {
            message: "Window not available".to_string(),
            code: Some("WINDOW_NOT_AVAILABLE".to_string()),
            field: None,
        })?;

        let credentials = window
            .navigator()
            .credentials()
            .map_err(|_| AuthError {
                message: "Credentials API not available".to_string(),
                code: Some("CREDENTIALS_API_NOT_AVAILABLE".to_string()),
                field: None,
            })?;

        let creation_options = self.build_creation_options(options)?;

        let credential_promise = credentials
            .create_with_options(&creation_options)
            .map_err(|e| AuthError {
                message: format!("Failed to create credential: {:?}", e),
                code: Some("CREDENTIAL_CREATION_FAILED".to_string()),
                field: None,
            })?;

        let credential = JsFuture::from(credential_promise)
            .await
            .map_err(|e| AuthError {
                message: format!("Credential creation promise failed: {:?}", e),
                code: Some("CREDENTIAL_PROMISE_FAILED".to_string()),
                field: None,
            })?;

        self.parse_registration_result(credential)
    }

    pub async fn authenticate_credential(
        &self,
        options: WebAuthnAuthenticationOptions,
    ) -> AuthResult<WebAuthnAuthenticationResult> {
        if !self.is_supported() {
            return Err(AuthError {
                message: "WebAuthn is not supported in this browser".to_string(),
                code: Some("WEBAUTHN_NOT_SUPPORTED".to_string()),
                field: None,
            });
        }

        let window = window().ok_or_else(|| AuthError {
            message: "Window not available".to_string(),
            code: Some("WINDOW_NOT_AVAILABLE".to_string()),
            field: None,
        })?;

        let credentials = window
            .navigator()
            .credentials()
            .map_err(|_| AuthError {
                message: "Credentials API not available".to_string(),
                code: Some("CREDENTIALS_API_NOT_AVAILABLE".to_string()),
                field: None,
            })?;

        let request_options = self.build_request_options(options)?;

        let credential_promise = credentials
            .get_with_options(&request_options)
            .map_err(|e| AuthError {
                message: format!("Failed to get credential: {:?}", e),
                code: Some("CREDENTIAL_GET_FAILED".to_string()),
                field: None,
            })?;

        let credential = JsFuture::from(credential_promise)
            .await
            .map_err(|e| AuthError {
                message: format!("Credential get promise failed: {:?}", e),
                code: Some("CREDENTIAL_PROMISE_FAILED".to_string()),
                field: None,
            })?;

        self.parse_authentication_result(credential)
    }

    fn build_creation_options(
        &self,
        options: WebAuthnRegistrationOptions,
    ) -> AuthResult<PublicKeyCredentialCreationOptions> {
        let creation_options = PublicKeyCredentialCreationOptions::new();

        // Set challenge
        let challenge_bytes = general_purpose::STANDARD
            .decode(&options.challenge)
            .map_err(|e| AuthError {
                message: format!("Failed to decode challenge: {}", e),
                code: Some("CHALLENGE_DECODE_ERROR".to_string()),
                field: None,
            })?;
        let challenge_array = Uint8Array::from(&challenge_bytes[..]);
        creation_options.set_challenge(&challenge_array);

        // Set relying party
        let rp = Object::new();
        js_sys::Reflect::set(&rp, &"id".into(), &options.rp.id.into()).unwrap();
        js_sys::Reflect::set(&rp, &"name".into(), &options.rp.name.into()).unwrap();
        creation_options.set_rp(&rp);

        // Set user
        let user = Object::new();
        let user_id_bytes = general_purpose::STANDARD
            .decode(&options.user.id)
            .map_err(|e| AuthError {
                message: format!("Failed to decode user ID: {}", e),
                code: Some("USER_ID_DECODE_ERROR".to_string()),
                field: None,
            })?;
        let user_id_array = Uint8Array::from(&user_id_bytes[..]);
        js_sys::Reflect::set(&user, &"id".into(), &user_id_array).unwrap();
        js_sys::Reflect::set(&user, &"name".into(), &options.user.name.into()).unwrap();
        js_sys::Reflect::set(&user, &"displayName".into(), &options.user.display_name.into()).unwrap();
        creation_options.set_user(&user);

        // Set public key credential parameters
        let cred_params = Array::new();
        for param in options.pub_key_cred_params {
            let param_obj = Object::new();
            js_sys::Reflect::set(&param_obj, &"type".into(), &param.type_.into()).unwrap();
            js_sys::Reflect::set(&param_obj, &"alg".into(), &param.alg.into()).unwrap();
            cred_params.push(&param_obj);
        }
        creation_options.set_pub_key_cred_params(&cred_params);

        // Set timeout
        if let Some(timeout) = options.timeout {
            creation_options.set_timeout(timeout);
        }

        // Set exclude credentials
        if let Some(exclude_creds) = options.exclude_credentials {
            let exclude_array = Array::new();
            for cred in exclude_creds {
                let cred_obj = Object::new();
                js_sys::Reflect::set(&cred_obj, &"type".into(), &cred.type_.into()).unwrap();

                let cred_id_bytes = general_purpose::STANDARD
                    .decode(&cred.id)
                    .map_err(|e| AuthError {
                        message: format!("Failed to decode credential ID: {}", e),
                        code: Some("CRED_ID_DECODE_ERROR".to_string()),
                        field: None,
                    })?;
                let cred_id_array = Uint8Array::from(&cred_id_bytes[..]);
                js_sys::Reflect::set(&cred_obj, &"id".into(), &cred_id_array).unwrap();

                if let Some(transports) = cred.transports {
                    let transports_array = Array::new();
                    for transport in transports {
                        transports_array.push(&transport.into());
                    }
                    js_sys::Reflect::set(&cred_obj, &"transports".into(), &transports_array).unwrap();
                }

                exclude_array.push(&cred_obj);
            }
            creation_options.set_exclude_credentials(&exclude_array);
        }

        // Set authenticator selection
        if let Some(auth_sel) = options.authenticator_selection {
            let auth_sel_obj = Object::new();

            if let Some(attachment) = auth_sel.authenticator_attachment {
                js_sys::Reflect::set(&auth_sel_obj, &"authenticatorAttachment".into(), &attachment.into()).unwrap();
            }

            if let Some(resident_key) = auth_sel.resident_key {
                js_sys::Reflect::set(&auth_sel_obj, &"residentKey".into(), &resident_key.into()).unwrap();
            }

            if let Some(require_resident_key) = auth_sel.require_resident_key {
                js_sys::Reflect::set(&auth_sel_obj, &"requireResidentKey".into(), &require_resident_key.into()).unwrap();
            }

            if let Some(user_verification) = auth_sel.user_verification {
                js_sys::Reflect::set(&auth_sel_obj, &"userVerification".into(), &user_verification.into()).unwrap();
            }

            creation_options.set_authenticator_selection(&auth_sel_obj);
        }

        // Set attestation
        if let Some(attestation) = options.attestation {
            creation_options.set_attestation(&attestation.into());
        }

        Ok(creation_options)
    }

    fn build_request_options(
        &self,
        options: WebAuthnAuthenticationOptions,
    ) -> AuthResult<PublicKeyCredentialRequestOptions> {
        let request_options = PublicKeyCredentialRequestOptions::new();

        // Set challenge
        let challenge_bytes = general_purpose::STANDARD
            .decode(&options.challenge)
            .map_err(|e| AuthError {
                message: format!("Failed to decode challenge: {}", e),
                code: Some("CHALLENGE_DECODE_ERROR".to_string()),
                field: None,
            })?;
        let challenge_array = Uint8Array::from(&challenge_bytes[..]);
        request_options.set_challenge(&challenge_array);

        // Set timeout
        if let Some(timeout) = options.timeout {
            request_options.set_timeout(timeout);
        }

        // Set RP ID
        if let Some(rp_id) = options.rp_id {
            request_options.set_rp_id(&rp_id);
        }

        // Set allow credentials
        if let Some(allow_creds) = options.allow_credentials {
            let allow_array = Array::new();
            for cred in allow_creds {
                let cred_obj = Object::new();
                js_sys::Reflect::set(&cred_obj, &"type".into(), &cred.type_.into()).unwrap();

                let cred_id_bytes = general_purpose::STANDARD
                    .decode(&cred.id)
                    .map_err(|e| AuthError {
                        message: format!("Failed to decode credential ID: {}", e),
                        code: Some("CRED_ID_DECODE_ERROR".to_string()),
                        field: None,
                    })?;
                let cred_id_array = Uint8Array::from(&cred_id_bytes[..]);
                js_sys::Reflect::set(&cred_obj, &"id".into(), &cred_id_array).unwrap();

                if let Some(transports) = cred.transports {
                    let transports_array = Array::new();
                    for transport in transports {
                        transports_array.push(&transport.into());
                    }
                    js_sys::Reflect::set(&cred_obj, &"transports".into(), &transports_array).unwrap();
                }

                allow_array.push(&cred_obj);
            }
            request_options.set_allow_credentials(&allow_array);
        }

        // Set user verification
        if let Some(user_verification) = options.user_verification {
            request_options.set_user_verification(&user_verification.into());
        }

        Ok(request_options)
    }

    fn parse_registration_result(
        &self,
        credential: JsValue,
    ) -> AuthResult<WebAuthnRegistrationResult> {
        let credential: PublicKeyCredential = credential
            .dyn_into()
            .map_err(|_| AuthError {
                message: "Invalid credential type".to_string(),
                code: Some("INVALID_CREDENTIAL_TYPE".to_string()),
                field: None,
            })?;

        let response: AuthenticatorAttestationResponse = credential
            .response()
            .dyn_into()
            .map_err(|_| AuthError {
                message: "Invalid response type".to_string(),
                code: Some("INVALID_RESPONSE_TYPE".to_string()),
                field: None,
            })?;

        let id = credential.id();
        let raw_id = general_purpose::STANDARD.encode(Uint8Array::from(credential.raw_id()).to_vec());
        let type_ = credential.type_();

        let client_data_json = general_purpose::STANDARD.encode(
            Uint8Array::from(response.client_data_json()).to_vec()
        );
        let attestation_object = general_purpose::STANDARD.encode(
            Uint8Array::from(response.attestation_object()).to_vec()
        );

        Ok(WebAuthnRegistrationResult {
            id,
            raw_id,
            type_,
            response: AttestationResponse {
                client_data_json,
                attestation_object,
            },
        })
    }

    fn parse_authentication_result(
        &self,
        credential: JsValue,
    ) -> AuthResult<WebAuthnAuthenticationResult> {
        let credential: PublicKeyCredential = credential
            .dyn_into()
            .map_err(|_| AuthError {
                message: "Invalid credential type".to_string(),
                code: Some("INVALID_CREDENTIAL_TYPE".to_string()),
                field: None,
            })?;

        let response: AuthenticatorAssertionResponse = credential
            .response()
            .dyn_into()
            .map_err(|_| AuthError {
                message: "Invalid response type".to_string(),
                code: Some("INVALID_RESPONSE_TYPE".to_string()),
                field: None,
            })?;

        let id = credential.id();
        let raw_id = general_purpose::STANDARD.encode(Uint8Array::from(credential.raw_id()).to_vec());
        let type_ = credential.type_();

        let client_data_json = general_purpose::STANDARD.encode(
            Uint8Array::from(response.client_data_json()).to_vec()
        );
        let authenticator_data = general_purpose::STANDARD.encode(
            Uint8Array::from(response.authenticator_data()).to_vec()
        );
        let signature = general_purpose::STANDARD.encode(
            Uint8Array::from(response.signature()).to_vec()
        );

        let user_handle = response.user_handle().map(|handle| {
            general_purpose::STANDARD.encode(Uint8Array::from(handle).to_vec())
        });

        Ok(WebAuthnAuthenticationResult {
            id,
            raw_id,
            type_,
            response: AssertionResponse {
                client_data_json,
                authenticator_data,
                signature,
                user_handle,
            },
        })
    }

    pub fn get_supported_authenticator_types(&self) -> Vec<String> {
        let mut types = Vec::new();

        if self.is_platform_authenticator_available() {
            types.push("platform".to_string());
        }

        if self.is_cross_platform_authenticator_available() {
            types.push("cross-platform".to_string());
        }

        types
    }

    pub fn is_platform_authenticator_available(&self) -> bool {
        // Check if platform authenticator (like Touch ID, Face ID, Windows Hello) is available
        window()
            .and_then(|w| {
                let promise = js_sys::Reflect::get(&w.navigator(), &"credentials".into())
                    .ok()
                    .and_then(|creds| {
                        js_sys::Reflect::get(&creds, &"isUserVerifyingPlatformAuthenticatorAvailable".into())
                            .ok()
                    });

                promise.and_then(|func| {
                    if func.is_function() {
                        // This would normally return a Promise<boolean>, but for simplicity
                        // we'll return true if the function exists
                        Some(true)
                    } else {
                        None
                    }
                })
            })
            .unwrap_or(false)
    }

    pub fn is_cross_platform_authenticator_available(&self) -> bool {
        // Cross-platform authenticators (USB security keys) are generally available
        // if WebAuthn is supported
        self.is_supported()
    }
}
