//! Node.js native bindings via napi-rs.
//!
//! Exposes the LastID SDK to Node.js/Bun as a native addon (.node file).
//! Same core code as the WASM build, just different FFI layer.
//!
//! # Usage from JavaScript/TypeScript
//!
//! ```javascript
//! const { LastIDClient } = require('@lastid/lastid-sdk');
//!
//! const client = new LastIDClient({
//!   idpEndpoint: 'https://human.lastid.co',
//!   clientId: 'your-client-id',
//! });
//!
//! // Request a base credential (returns QR-encodable URI)
//! const response = await client.requestCredential('https://your-app.com/cb');
//! console.log(response.requestUri); // openid4vp://...
//!
//! // Poll for completion
//! const status = await client.pollForCompletion(response.requestId);
//! if (status.state === 'fulfilled') {
//!   const verified = await client.verifyPresentation(status.presentation);
//!   console.log(verified.subjectDid); // did:lastid:abc123
//! }
//! ```

#![cfg(all(feature = "napi", not(target_arch = "wasm32")))]

use napi::bindgen_prelude::*;
use napi_derive::napi;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::Mutex;

use crate::client::LastIDClient as CoreClient;
use crate::config::SDKConfig;
use crate::policies::BaseCredentialPolicy as CoreBasePolicy;
use crate::types::RequestId;

// ── Config ──────────────────────────────────────────────────────────

#[derive(Deserialize)]
#[napi(object)]
pub struct ClientConfig {
    pub idp_endpoint: String,
    pub client_id: String,
    pub timeout_seconds: Option<u32>,
}

// ── Response types ──────────────────────────────────────────────────

#[napi(object)]
pub struct CredentialRequestResponse {
    pub request_id: String,
    pub request_uri: String,
    pub expires_in: u32,
    pub nonce: String,
}

#[napi(object)]
pub struct RequestStatusResponse {
    pub state: String,
    pub request_id: String,
    pub presentation: Option<String>,
    pub error: Option<String>,
}

#[napi(object)]
pub struct VerifiedCredentialResponse {
    pub subject_did: String,
    pub issuer_did: String,
    pub credential_type: String,
    pub claims: serde_json::Value,
    pub issued_at: String,
    pub expires_at: String,
    pub issuer_status: String,
    pub signature_verified: bool,
}

// ── Helper: convert core response to napi response ──────────────────

fn core_response_to_napi(
    response: &crate::types::CredentialRequestResponse,
) -> CredentialRequestResponse {
    CredentialRequestResponse {
        request_id: response.request_id.to_string(),
        request_uri: response.request_uri.clone(),
        expires_in: u32::try_from(response.expires_in).unwrap_or(u32::MAX),
        nonce: response.nonce.clone(),
    }
}

// ── Helper: convert RequestStatus to response ───────────────────────

fn status_to_response(status: &crate::types::RequestStatus) -> RequestStatusResponse {
    match status {
        crate::types::RequestStatus::Pending { request_id, .. } => RequestStatusResponse {
            state: "pending".to_string(),
            request_id: request_id.to_string(),
            presentation: None,
            error: None,
        },
        crate::types::RequestStatus::Fulfilled {
            request_id,
            presentation,
            ..
        } => RequestStatusResponse {
            state: "fulfilled".to_string(),
            request_id: request_id.to_string(),
            presentation: Some(presentation.clone()),
            error: None,
        },
        crate::types::RequestStatus::Denied {
            request_id, reason, ..
        } => RequestStatusResponse {
            state: "denied".to_string(),
            request_id: request_id.to_string(),
            presentation: None,
            error: Some(reason.clone()),
        },
        crate::types::RequestStatus::Expired { request_id, .. } => RequestStatusResponse {
            state: "expired".to_string(),
            request_id: request_id.to_string(),
            presentation: None,
            error: Some("Request expired".to_string()),
        },
        crate::types::RequestStatus::NotFound {
            request_id,
            message,
            ..
        } => RequestStatusResponse {
            state: "not_found".to_string(),
            request_id: request_id.to_string(),
            presentation: None,
            error: Some(message.clone()),
        },
        crate::types::RequestStatus::Timeout {
            request_id,
            elapsed_seconds,
            ..
        } => RequestStatusResponse {
            state: "timeout".to_string(),
            request_id: request_id.to_string(),
            presentation: None,
            error: Some(format!("Timed out after {elapsed_seconds}s")),
        },
    }
}

// ── Client ──────────────────────────────────────────────────────────

#[napi]
pub struct NapiClient {
    inner: Arc<Mutex<CoreClient>>,
}

#[napi]
impl NapiClient {
    /// Create a new LastID client for Node.js/Bun.
    ///
    /// @param config - Client configuration with idpEndpoint and clientId
    #[napi(constructor)]
    pub fn new(config: ClientConfig) -> Result<Self> {
        let sdk_config = SDKConfig {
            idp_endpoint: config.idp_endpoint,
            client_id: config.client_id,
            timeout_seconds: u64::from(config.timeout_seconds.unwrap_or(30)),
            ..SDKConfig::default()
        };

        let client = CoreClient::new(sdk_config)
            .map_err(|e| Error::from_reason(format!("Failed to create client: {e}")))?;

        Ok(Self {
            inner: Arc::new(Mutex::new(client)),
        })
    }

    // ── Base credential ─────────────────────────────────────────────

    /// Request a base credential presentation.
    /// Returns a response with requestUri (openid4vp:// URL for QR code).
    ///
    /// @param callbackUrl - HTTPS URL for redirect after user completes request
    /// @param state - Optional CSRF protection token
    /// @param nonce - Optional cryptographic binding nonce
    #[napi]
    pub async fn request_credential(
        &self,
        callback_url: String,
        state: Option<String>,
        nonce: Option<String>,
    ) -> Result<CredentialRequestResponse> {
        let mut policy = CoreBasePolicy::new();

        if let Some(s) = state {
            policy = policy.with_state(&s);
        }
        if let Some(n) = nonce {
            policy = policy.with_nonce(&n);
        }

        let policy = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(policy)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Persona credential ──────────────────────────────────────────

    /// Request a persona credential presentation.
    ///
    /// @param callbackUrl - HTTPS callback URL (required)
    /// @param fields - Optional required field names (display_name, email, phone, company, etc.)
    /// @param optionalFields - Optional field names the user may choose to disclose
    /// @param state - Optional CSRF token
    /// @param nonce - Optional binding nonce
    #[cfg(feature = "persona-policy")]
    #[napi]
    pub async fn request_persona_credential(
        &self,
        callback_url: String,
        fields: Option<Vec<String>>,
        optional_fields: Option<Vec<String>>,
    ) -> Result<CredentialRequestResponse> {
        use crate::policies::{PersonaField, PersonaPolicy};

        fn parse_persona_field(name: &str) -> Result<PersonaField> {
            match name {
                "display_name" => Ok(PersonaField::DisplayName),
                "email" => Ok(PersonaField::Email),
                "phone" => Ok(PersonaField::Phone),
                "company" => Ok(PersonaField::Company),
                "job_title" => Ok(PersonaField::JobTitle),
                "avatar_url" => Ok(PersonaField::AvatarUrl),
                "website" => Ok(PersonaField::Website),
                "location" => Ok(PersonaField::Location),
                "visibility" => Ok(PersonaField::Visibility),
                other => Err(Error::from_reason(format!(
                    "Unknown persona field: {other}"
                ))),
            }
        }

        let mut policy = PersonaPolicy::new();

        if let Some(ref field_names) = fields {
            let parsed: std::result::Result<Vec<PersonaField>, _> =
                field_names.iter().map(|f| parse_persona_field(f)).collect();
            policy = policy.with_fields(&parsed?);
        }
        if let Some(ref field_names) = optional_fields {
            let parsed: std::result::Result<Vec<PersonaField>, _> =
                field_names.iter().map(|f| parse_persona_field(f)).collect();
            policy = policy.with_optional_fields(&parsed?);
        }

        let complete = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(complete)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Verified email credential ───────────────────────────────────

    /// Request a verified email credential presentation.
    ///
    /// @param callbackUrl - HTTPS callback URL (required)
    /// @param domainConstraint - Optional domain pattern (e.g., "example.com")
    /// @param state - Optional CSRF token
    /// @param nonce - Optional binding nonce
    #[cfg(feature = "verified-email-policy")]
    #[napi]
    pub async fn request_verified_email_credential(
        &self,
        callback_url: String,
        domain_constraint: Option<String>,
        state: Option<String>,
        nonce: Option<String>,
    ) -> Result<CredentialRequestResponse> {
        use crate::policies::VerifiedEmailPolicy;

        let mut policy = VerifiedEmailPolicy::new();
        if let Some(domain) = domain_constraint {
            policy = policy.with_domain_constraint(&domain);
        }

        let _ = (state, nonce); // VerifiedEmailPolicy doesn't have state/nonce on Incomplete
        let complete = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(complete)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Verified phone credential ───────────────────────────────────

    /// Request a verified phone credential presentation.
    ///
    /// @param callbackUrl - HTTPS callback URL (required)
    /// @param countryCode - Optional country code filter (e.g., "+1")
    /// @param state - Optional CSRF token
    /// @param nonce - Optional binding nonce
    #[cfg(feature = "verified-phone-policy")]
    #[napi]
    pub async fn request_verified_phone_credential(
        &self,
        callback_url: String,
        country_code: Option<String>,
        state: Option<String>,
        nonce: Option<String>,
    ) -> Result<CredentialRequestResponse> {
        use crate::policies::VerifiedPhonePolicy;

        let mut policy = VerifiedPhonePolicy::new();
        if let Some(cc) = country_code {
            policy = policy.with_country_code(&cc);
        }

        let _ = (state, nonce);
        let complete = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(complete)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Employment credential ───────────────────────────────────────

    /// Request an employment credential presentation.
    ///
    /// @param callbackUrl - HTTPS callback URL (required)
    /// @param employerDomain - Optional employer domain filter
    /// @param state - Optional CSRF token
    /// @param nonce - Optional binding nonce
    #[cfg(feature = "employment-policy")]
    #[napi]
    pub async fn request_employment_credential(
        &self,
        callback_url: String,
        employer_domain: Option<String>,
        state: Option<String>,
        nonce: Option<String>,
    ) -> Result<CredentialRequestResponse> {
        use crate::policies::EmploymentPolicy;

        let mut policy = EmploymentPolicy::new();
        if let Some(domain) = employer_domain {
            policy = policy.with_employer_domain(&domain);
        }

        let _ = (state, nonce);
        let complete = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(complete)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Trust credential ────────────────────────────────────────────

    /// Request a trust credential presentation.
    ///
    /// @param callbackUrl - HTTPS callback URL (required)
    /// @param minimumLevel - Optional minimum trust level (0-4)
    /// @param state - Optional CSRF token
    /// @param nonce - Optional binding nonce
    #[cfg(feature = "trust-policy")]
    #[napi]
    pub async fn request_trust_credential(
        &self,
        callback_url: String,
        minimum_level: Option<u32>,
        state: Option<String>,
        nonce: Option<String>,
    ) -> Result<CredentialRequestResponse> {
        use crate::policies::TrustPolicy;

        let mut policy = TrustPolicy::new();
        if let Some(level) = minimum_level {
            policy = policy.with_minimum_level_value(i64::from(level));
        }

        let _ = (state, nonce);
        let complete = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(complete)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Age proof credential ────────────────────────────────────────

    /// Request an age proof credential presentation.
    ///
    /// @param callbackUrl - HTTPS callback URL (required)
    /// @param minimumAge - Optional minimum age requirement
    /// @param state - Optional CSRF token
    /// @param nonce - Optional binding nonce
    #[cfg(feature = "age-proof-policy")]
    #[napi]
    pub async fn request_age_proof_credential(
        &self,
        callback_url: String,
        minimum_age: Option<u32>,
        state: Option<String>,
        nonce: Option<String>,
    ) -> Result<CredentialRequestResponse> {
        use crate::policies::AgeProofPolicy;

        let mut policy = AgeProofPolicy::new();
        if let Some(age) = minimum_age {
            let age_u8 = u8::try_from(age)
                .map_err(|_| Error::from_reason("Age must be 0-255".to_string()))?;
            policy = policy.with_minimum_age(age_u8);
        }

        let _ = (state, nonce);
        let complete = policy.with_callback(&callback_url);

        let client = self.inner.lock().await;
        let response = client
            .request_credential(complete)
            .await
            .map_err(|e| Error::from_reason(format!("Request failed: {e}")))?;

        Ok(core_response_to_napi(&response))
    }

    // ── Polling ─────────────────────────────────────────────────────

    /// Poll for credential request status (single check).
    #[napi]
    pub async fn poll_status(&self, request_id: String) -> Result<RequestStatusResponse> {
        let rid = RequestId::new(request_id);
        let client = self.inner.lock().await;
        let status = client
            .poll_request(&rid)
            .await
            .map_err(|e| Error::from_reason(format!("Poll failed: {e}")))?;

        Ok(status_to_response(&status))
    }

    /// Poll until the request completes or times out (HTTP polling with backoff).
    #[napi]
    pub async fn poll_for_completion(&self, request_id: String) -> Result<RequestStatusResponse> {
        let rid = RequestId::new(request_id);
        let client = self.inner.lock().await;
        let status = client
            .poll_for_completion(&rid)
            .await
            .map_err(|e| Error::from_reason(format!("Poll failed: {e}")))?;

        Ok(status_to_response(&status))
    }

    /// Subscribe for real-time status updates via WebSocket.
    /// Falls back to HTTP polling if WebSocket connection fails.
    /// This is the preferred method — faster than polling.
    #[cfg(feature = "websocket")]
    #[napi]
    pub async fn subscribe_for_completion(
        &self,
        request_id: String,
    ) -> Result<RequestStatusResponse> {
        let rid = RequestId::new(request_id);
        let client = self.inner.lock().await;
        let status = client
            .subscribe_for_completion(&rid)
            .await
            .map_err(|e| Error::from_reason(format!("Subscribe failed: {e}")))?;

        Ok(status_to_response(&status))
    }

    // ── Verification ────────────────────────────────────────────────

    /// Verify a credential presentation string (SD-JWT).
    #[napi]
    pub async fn verify_presentation(
        &self,
        presentation: String,
    ) -> Result<VerifiedCredentialResponse> {
        let client = self.inner.lock().await;
        let verified = client
            .verify_presentation(&presentation)
            .await
            .map_err(|e| Error::from_reason(format!("Verification failed: {e}")))?;

        Ok(VerifiedCredentialResponse {
            subject_did: verified.subject_did,
            issuer_did: verified.issuer_did,
            credential_type: verified.credential_type,
            claims: verified.claims,
            issued_at: verified.issued_at.to_rfc3339(),
            expires_at: verified.expires_at.to_rfc3339(),
            issuer_status: format!("{:?}", verified.issuer_status),
            signature_verified: verified.signature_verified,
        })
    }

    // ── Utilities ───────────────────────────────────────────────────

    /// Get the SDK version.
    #[napi]
    pub fn version() -> String {
        env!("CARGO_PKG_VERSION").to_string()
    }
}
