//! Verified persona credential policy builder.
//!
//! Combines persona information with identity verification.

use std::collections::HashMap;
use std::marker::PhantomData;

use serde::{Deserialize, Serialize};

use super::{Complete, CredentialConstraint, Incomplete, PolicyBuilder};
use crate::error::LastIDError;
use crate::types::{CredentialPolicyRequest, EndpointUrl};

/// Verification level for persona credentials.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum VerificationLevel {
    /// Basic verification (email/phone confirmed)
    Basic,
    /// Standard verification (document checked)
    Standard,
    /// Enhanced verification (biometric + document)
    Enhanced,
}

impl VerificationLevel {
    /// Get the level as a string for the API.
    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::Basic => "basic",
            Self::Standard => "standard",
            Self::Enhanced => "enhanced",
        }
    }
}

/// Fields that can be requested in a verified persona credential.
///
/// These field names match the actual `LastID.VerifiedPersona` schema in the IDP.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum VerifiedPersonaField {
    /// User's full legal name
    LegalName,
    /// User's first/given name
    FirstName,
    /// User's last/family name
    LastName,
    /// User's date of birth
    DateOfBirth,
    /// User's age (derived from date of birth)
    Age,
    /// User's nationality
    Nationality,
    /// User's residential address (free text)
    ResidentialAddress,
    /// Structured address components
    StructuredAddress,
    /// Type of government ID used
    GovernmentIdType,
    /// ID document number (partially masked)
    DocumentNumber,
    /// Liveness check confidence score
    LivenessScore,
    /// Face match confidence score
    FaceMatchScore,
}

impl VerifiedPersonaField {
    /// Get the field name as a string for the API.
    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::LegalName => "legal_name",
            Self::FirstName => "first_name",
            Self::LastName => "last_name",
            Self::DateOfBirth => "date_of_birth",
            Self::Age => "age",
            Self::Nationality => "nationality",
            Self::ResidentialAddress => "residential_address",
            Self::StructuredAddress => "structured_address",
            Self::GovernmentIdType => "government_id_type",
            Self::DocumentNumber => "document_number",
            Self::LivenessScore => "liveness_score",
            Self::FaceMatchScore => "face_match_score",
        }
    }
}

/// Policy builder for verified persona credentials.
///
/// # Type-State Pattern
///
/// - `VerifiedPersonaPolicy<Incomplete>` - Initial state
/// - `VerifiedPersonaPolicy<Complete>` - Ready to build
///
/// # Required vs Optional Fields
///
/// - **Required fields** (`with_fields`): Must be present in the credential
/// - **Optional fields** (`with_optional_fields`): User can choose to disclose
///
/// # Example
///
/// ```rust,no_run
/// use lastid_sdk::{
///     PolicyBuilder, VerificationLevel, VerifiedPersonaField,
///     VerifiedPersonaPolicy,
/// };
///
/// let policy = VerifiedPersonaPolicy::new()
///     .with_verification_level(VerificationLevel::Enhanced)
///     .with_fields(&[VerifiedPersonaField::LegalName])
///     .with_optional_fields(&[
///         VerifiedPersonaField::DateOfBirth,
///         VerifiedPersonaField::Nationality,
///     ])
///     .with_callback("https://example.com/callback");
///
/// let request = policy.to_request().unwrap();
/// ```
#[cfg(feature = "verified-persona-policy")]
pub struct VerifiedPersonaPolicy<State = Incomplete> {
    callback_url: Option<String>,
    fields: Vec<VerifiedPersonaField>,
    optional_fields: Vec<VerifiedPersonaField>,
    verification_level: Option<VerificationLevel>,
    email_domain: Option<String>,
    _marker: PhantomData<State>,
}

#[cfg(feature = "verified-persona-policy")]
impl Default for VerifiedPersonaPolicy<Incomplete> {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "verified-persona-policy")]
impl VerifiedPersonaPolicy<Incomplete> {
    /// Create a new verified persona credential policy builder.
    #[must_use]
    pub const fn new() -> Self {
        Self {
            callback_url: None,
            fields: Vec::new(),
            optional_fields: Vec::new(),
            verification_level: None,
            email_domain: None,
            _marker: PhantomData,
        }
    }

    /// Set the required verification level.
    #[must_use]
    pub const fn with_verification_level(mut self, level: VerificationLevel) -> Self {
        self.verification_level = Some(level);
        self
    }

    /// Add required fields to request in the verified persona credential.
    ///
    /// Required fields must be present in the credential (no `optional: true` flag).
    #[must_use]
    pub fn with_fields(mut self, fields: &[VerifiedPersonaField]) -> Self {
        self.fields.extend(fields.iter().copied());
        self
    }

    /// Add a single required field to request.
    #[must_use]
    pub fn with_field(mut self, field: VerifiedPersonaField) -> Self {
        self.fields.push(field);
        self
    }

    /// Add optional fields for selective disclosure.
    ///
    /// Optional fields have `optional: true` flag, allowing the user to choose
    /// whether to disclose them.
    #[must_use]
    pub fn with_optional_fields(mut self, fields: &[VerifiedPersonaField]) -> Self {
        self.optional_fields.extend(fields.iter().copied());
        self
    }

    /// Add a single optional field for selective disclosure.
    #[must_use]
    pub fn with_optional_field(mut self, field: VerifiedPersonaField) -> Self {
        self.optional_fields.push(field);
        self
    }

    /// Filter to verified personas with email addresses from a specific domain.
    ///
    /// This is useful for enterprise applications that want to verify users are
    /// from a specific organization based on their email domain.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use lastid_sdk::VerifiedPersonaPolicy;
    ///
    /// // Only accept verified personas with @bigcorp.com email
    /// let policy = VerifiedPersonaPolicy::new()
    ///     .with_email_domain("bigcorp.com")
    ///     .with_callback("https://example.com/callback");
    /// ```
    ///
    /// # Errors
    ///
    /// Returns `PolicyValidation` error at build time if the domain format is
    /// invalid (e.g., contains `@` or `/`).
    #[must_use]
    pub fn with_email_domain(mut self, domain: impl Into<String>) -> Self {
        self.email_domain = Some(domain.into());
        self
    }

    /// Set the callback URL and transition to Complete state.
    #[must_use]
    pub fn with_callback(self, url: impl Into<String>) -> VerifiedPersonaPolicy<Complete> {
        VerifiedPersonaPolicy {
            callback_url: Some(url.into()),
            fields: self.fields,
            optional_fields: self.optional_fields,
            verification_level: self.verification_level,
            email_domain: self.email_domain,
            _marker: PhantomData,
        }
    }
}

#[cfg(feature = "verified-persona-policy")]
impl PolicyBuilder for VerifiedPersonaPolicy<Complete> {
    fn credential_type(&self) -> &'static str {
        "LastID.VerifiedPersona"
    }

    fn to_request(self) -> Result<CredentialPolicyRequest, LastIDError> {
        // SEC-001: Validate callback URL with HTTPS enforcement
        // HTTP is only allowed for localhost (development)
        if let Some(ref url) = self.callback_url {
            EndpointUrl::new(url)?;
        }

        // Validate email domain if present
        if let Some(ref domain) = self.email_domain {
            CredentialConstraint::validate_domain(domain)?;
        }

        // VerifiedPersona requires Base -> VerifiedPersona chain (FR-007)
        let mut request = CredentialPolicyRequest::with_chain(self.credential_type());
        request.callback_url = self.callback_url;

        // Build constraints
        let mut constraints_map = HashMap::new();

        if let Some(level) = self.verification_level {
            constraints_map.insert(
                "verificationLevel".to_string(),
                serde_json::json!(level.as_str()),
            );
        }

        if let Some(ref domain) = self.email_domain {
            constraints_map.insert("email_domain".to_string(), serde_json::json!(domain));
        }

        if !constraints_map.is_empty() {
            request.constraints = Some(constraints_map);
        }

        // Set required fields (no optional flag in OpenID4VP)
        if !self.fields.is_empty() {
            request.required_fields =
                Some(self.fields.iter().map(|f| f.as_str().to_string()).collect());
        }

        // Set optional fields (optional: true in OpenID4VP)
        if !self.optional_fields.is_empty() {
            request.optional_fields = Some(
                self.optional_fields
                    .iter()
                    .map(|f| f.as_str().to_string())
                    .collect(),
            );
        }

        Ok(request)
    }
}

#[cfg(feature = "verified-persona-policy")]
impl Clone for VerifiedPersonaPolicy<Complete> {
    fn clone(&self) -> Self {
        Self {
            callback_url: self.callback_url.clone(),
            fields: self.fields.clone(),
            optional_fields: self.optional_fields.clone(),
            verification_level: self.verification_level,
            email_domain: self.email_domain.clone(),
            _marker: PhantomData,
        }
    }
}

#[cfg(all(test, feature = "verified-persona-policy"))]
mod tests {
    use super::*;

    #[test]
    fn test_verified_persona_policy_build() {
        let policy = VerifiedPersonaPolicy::new()
            .with_verification_level(VerificationLevel::Enhanced)
            .with_fields(&[VerifiedPersonaField::LegalName])
            .with_optional_fields(&[
                VerifiedPersonaField::DateOfBirth,
                VerifiedPersonaField::Nationality,
            ])
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        assert_eq!(request.credential_type, "LastID.VerifiedPersona");
        assert!(request.constraints.is_some());

        let constraints = request.constraints.unwrap();
        assert!(constraints.get("verificationLevel").is_some());

        // Fields are now in required_fields/optional_fields, not constraints
        assert_eq!(
            request.required_fields,
            Some(vec!["legal_name".to_string()])
        );
        assert_eq!(
            request.optional_fields,
            Some(vec!["date_of_birth".to_string(), "nationality".to_string()])
        );
    }

    #[test]
    fn test_verified_persona_no_constraints() {
        let policy = VerifiedPersonaPolicy::new().with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        assert!(request.constraints.is_none());
        assert!(request.required_fields.is_none());
        assert!(request.optional_fields.is_none());
    }

    #[test]
    fn test_verification_level_as_str() {
        assert_eq!(VerificationLevel::Basic.as_str(), "basic");
        assert_eq!(VerificationLevel::Standard.as_str(), "standard");
        assert_eq!(VerificationLevel::Enhanced.as_str(), "enhanced");
    }

    #[test]
    fn test_verified_persona_field_as_str() {
        assert_eq!(VerifiedPersonaField::LegalName.as_str(), "legal_name");
        assert_eq!(VerifiedPersonaField::DateOfBirth.as_str(), "date_of_birth");
        assert_eq!(VerifiedPersonaField::Nationality.as_str(), "nationality");
        assert_eq!(VerifiedPersonaField::FirstName.as_str(), "first_name");
        assert_eq!(VerifiedPersonaField::LastName.as_str(), "last_name");
    }

    #[test]
    fn test_with_email_domain() {
        let policy = VerifiedPersonaPolicy::new()
            .with_email_domain("bigcorp.com")
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(
            constraints.get("email_domain"),
            Some(&serde_json::json!("bigcorp.com"))
        );
    }

    #[test]
    fn test_invalid_email_domain() {
        let policy = VerifiedPersonaPolicy::new()
            .with_email_domain("user@bigcorp.com")
            .with_callback("https://example.com");

        let result = policy.to_request();
        assert!(result.is_err());
    }

    #[test]
    fn test_combined_verified_persona_constraints() {
        let policy = VerifiedPersonaPolicy::new()
            .with_verification_level(VerificationLevel::Enhanced)
            .with_email_domain("corp.com")
            .with_fields(&[VerifiedPersonaField::LegalName])
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();

        assert_eq!(
            constraints.get("verificationLevel"),
            Some(&serde_json::json!("enhanced"))
        );
        assert_eq!(
            constraints.get("email_domain"),
            Some(&serde_json::json!("corp.com"))
        );
        assert_eq!(
            request.required_fields,
            Some(vec!["legal_name".to_string()])
        );
    }
}
