//! Credential policy request type definition.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

/// Serializable credential policy request.
///
/// This is the payload sent to the IDP when requesting credentials.
/// Non-root credentials automatically include their parent credentials in the
/// chain.
///
/// # `OpenID4VP` Request Fields
///
/// The `state` and `nonce` fields are standard `OpenID4VP` request parameters:
/// - **`state`**: CSRF protection token, returned unchanged in the response
/// - **`nonce`**: Cryptographic binding value for key binding proofs
///
/// These are top-level request fields, NOT credential claim constraints.
///
/// # Required vs Optional Fields
///
/// - **`required_fields`**: Fields that MUST be present in the credential
///   (no `optional: true` flag in `OpenID4VP`)
/// - **`optional_fields`**: Fields that the user MAY choose to disclose
///   (with `optional: true` flag in `OpenID4VP`)
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CredentialPolicyRequest {
    /// OAuth client ID for this verification request.
    /// Not serialized - used internally to pass `client_id` to IDP client.
    #[serde(skip)]
    pub client_id: String,

    /// Credential type (e.g., "LastID.Base", "LastID.VerifiedEmail")
    /// This is the primary/leaf credential type being requested.
    pub credential_type: String,

    /// Full credential chain including parent credentials (FR-007).
    /// For non-root credentials, this includes all prerequisites.
    /// Example: `Employment` requires `Base`, `Persona`, `Employment`.
    #[serde(skip_serializing_if = "credential_chain_is_single")]
    pub credential_types: Vec<String>,

    /// Callback URL for redirect after user completes request.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub callback_url: Option<String>,

    /// `OpenID4VP` state parameter for CSRF protection.
    ///
    /// This value is returned unchanged in the authorization response,
    /// allowing the client to verify the response matches the request.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state: Option<String>,

    /// `OpenID4VP` nonce for cryptographic binding.
    ///
    /// Used in key binding proofs to ensure freshness and prevent replay
    /// attacks. The wallet includes this nonce in any key binding JWT it
    /// generates.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nonce: Option<String>,

    /// Fields that MUST be disclosed (presence required).
    ///
    /// These become field entries in `presentation_definition.constraints.fields`
    /// WITHOUT the `optional: true` flag.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required_fields: Option<Vec<String>>,

    /// Fields that MAY be disclosed (user chooses via selective disclosure).
    ///
    /// These become field entries in `presentation_definition.constraints.fields`
    /// WITH the `optional: true` flag set.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub optional_fields: Option<Vec<String>>,

    /// Optional constraints for the credential request (claim-level
    /// requirements).
    ///
    /// These become field constraints in the `presentation_definition`,
    /// requiring specific claim values in the presented credential.
    /// Note: `requestedFields` key is deprecated; use `required_fields` instead.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub constraints: Option<HashMap<String, serde_json::Value>>,
}

/// Skip serializing `credential_types` if it only contains the primary type (no
/// chain needed).
const fn credential_chain_is_single(chain: &[String]) -> bool {
    chain.len() <= 1
}

/// Credential type hierarchy for automatic parent chain inclusion (FR-007).
///
/// This module defines the parent-child relationships between credential types.
/// When requesting a non-root credential, all parent credentials are
/// automatically included.
pub mod credential_chain {
    /// `LastID` credential type constants.
    pub mod types {
        pub const BASE: &str = "LastID.Base";
        pub const PERSONA: &str = "LastID.Persona";
        pub const VERIFIED_EMAIL: &str = "LastID.VerifiedEmail";
        pub const VERIFIED_PHONE: &str = "LastID.VerifiedPhone";
        pub const VERIFIED_PERSONA: &str = "LastID.VerifiedPersona";
        pub const EMPLOYMENT: &str = "LastID.Employment";
        pub const TRUST: &str = "LastID.Trust";
        pub const AGE_PROOF: &str = "LastID.AgeProof";
    }

    /// Get the full credential chain for a credential type.
    ///
    /// Returns a Vec containing all parent credentials plus the requested type,
    /// in order from root to leaf.
    ///
    /// # Credential Hierarchy
    ///
    /// ```text
    /// LastID.Base (root)
    ///   ├── LastID.Persona
    ///   │     └── LastID.Employment
    ///   ├── LastID.VerifiedEmail
    ///   ├── LastID.VerifiedPhone
    ///   ├── LastID.VerifiedPersona
    ///   │     └── LastID.AgeProof
    ///   └── LastID.Trust
    /// ```
    ///
    /// # Examples
    ///
    /// ```rust
    /// use lastid_sdk::types::credential_chain::get_credential_chain;
    ///
    /// // Base credential has no parents
    /// assert_eq!(get_credential_chain("LastID.Base"), vec!["LastID.Base"]);
    ///
    /// // VerifiedEmail requires Base -> VerifiedEmail (direct child of Base)
    /// assert_eq!(
    ///     get_credential_chain("LastID.VerifiedEmail"),
    ///     vec!["LastID.Base", "LastID.VerifiedEmail"]
    /// );
    /// ```
    #[must_use]
    pub fn get_credential_chain(credential_type: &str) -> Vec<String> {
        match credential_type {
            // Root credential - no parents
            types::BASE => vec![types::BASE.to_string()],

            // Level 1: Direct children of Base
            types::PERSONA => vec![types::BASE.to_string(), types::PERSONA.to_string()],
            types::VERIFIED_EMAIL => {
                vec![types::BASE.to_string(), types::VERIFIED_EMAIL.to_string()]
            }
            types::VERIFIED_PHONE => {
                vec![types::BASE.to_string(), types::VERIFIED_PHONE.to_string()]
            }
            types::VERIFIED_PERSONA => {
                vec![types::BASE.to_string(), types::VERIFIED_PERSONA.to_string()]
            }
            types::TRUST => vec![types::BASE.to_string(), types::TRUST.to_string()],

            // Level 2: Employment is child of Persona
            types::EMPLOYMENT => vec![
                types::BASE.to_string(),
                types::PERSONA.to_string(),
                types::EMPLOYMENT.to_string(),
            ],

            // Level 2: AgeProof is child of VerifiedPersona
            types::AGE_PROOF => vec![
                types::BASE.to_string(),
                types::VERIFIED_PERSONA.to_string(),
                types::AGE_PROOF.to_string(),
            ],

            // Unknown type - return as-is (single element chain)
            _ => vec![credential_type.to_string()],
        }
    }

    /// Check if a credential type is a root credential (has no parents).
    #[must_use]
    pub fn is_root_credential(credential_type: &str) -> bool {
        credential_type == types::BASE
    }

    /// Get the parent credential type for a given credential type.
    /// Returns `None` for root credentials or unknown types.
    #[must_use]
    pub fn get_parent_credential(credential_type: &str) -> Option<&'static str> {
        match credential_type {
            // Direct children of Base
            types::PERSONA
            | types::VERIFIED_EMAIL
            | types::VERIFIED_PHONE
            | types::VERIFIED_PERSONA
            | types::TRUST => Some(types::BASE),
            // Employment is child of Persona
            types::EMPLOYMENT => Some(types::PERSONA),
            // AgeProof is child of VerifiedPersona
            types::AGE_PROOF => Some(types::VERIFIED_PERSONA),
            // Root credentials and unknown types have no parent
            _ => None,
        }
    }
}

impl CredentialPolicyRequest {
    /// Create a new credential policy request for a root credential.
    #[must_use]
    pub fn new(credential_type: impl Into<String>) -> Self {
        let cred_type = credential_type.into();
        Self {
            client_id: String::new(),
            credential_types: vec![cred_type.clone()],
            credential_type: cred_type,
            callback_url: None,
            state: None,
            nonce: None,
            required_fields: None,
            optional_fields: None,
            constraints: None,
        }
    }

    /// Create a new credential policy request with automatic parent chain
    /// inclusion (FR-007).
    ///
    /// For non-root credentials, this automatically includes all parent
    /// credentials in the chain. For example, `Employment` will include
    /// `Base` and `Persona`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use lastid_sdk::types::CredentialPolicyRequest;
    ///
    /// // Employment automatically includes Base and Persona
    /// let request = CredentialPolicyRequest::with_chain("LastID.Employment");
    /// assert_eq!(
    ///     request.credential_types,
    ///     vec!["LastID.Base", "LastID.Persona", "LastID.Employment"]
    /// );
    ///
    /// // VerifiedEmail is a direct child of Base
    /// let request = CredentialPolicyRequest::with_chain("LastID.VerifiedEmail");
    /// assert_eq!(
    ///     request.credential_types,
    ///     vec!["LastID.Base", "LastID.VerifiedEmail"]
    /// );
    /// ```
    #[must_use]
    pub fn with_chain(credential_type: impl Into<String>) -> Self {
        let cred_type = credential_type.into();
        let chain = credential_chain::get_credential_chain(&cred_type);
        Self {
            client_id: String::new(),
            credential_type: cred_type,
            credential_types: chain,
            callback_url: None,
            state: None,
            nonce: None,
            required_fields: None,
            optional_fields: None,
            constraints: None,
        }
    }

    /// Set the callback URL.
    #[must_use]
    pub fn with_callback(mut self, url: impl Into<String>) -> Self {
        self.callback_url = Some(url.into());
        self
    }

    /// Set the `OpenID4VP` state parameter for CSRF protection.
    ///
    /// The state value is returned unchanged in the authorization response,
    /// allowing the client to verify the response matches the request.
    ///
    /// # Example
    ///
    /// ```rust
    /// use lastid_sdk::types::CredentialPolicyRequest;
    ///
    /// let request = CredentialPolicyRequest::with_chain("LastID.Base")
    ///     .with_state("csrf-token-12345");
    ///
    /// assert_eq!(request.state, Some("csrf-token-12345".to_string()));
    /// ```
    #[must_use]
    pub fn with_state(mut self, state: impl Into<String>) -> Self {
        self.state = Some(state.into());
        self
    }

    /// Set the `OpenID4VP` nonce for cryptographic binding.
    ///
    /// The nonce is used in key binding proofs to ensure freshness and
    /// prevent replay attacks. The wallet includes this nonce in any
    /// key binding JWT it generates.
    ///
    /// # Example
    ///
    /// ```rust
    /// use lastid_sdk::types::CredentialPolicyRequest;
    ///
    /// let request = CredentialPolicyRequest::with_chain("LastID.Base")
    ///     .with_nonce("random-nonce-67890");
    ///
    /// assert_eq!(request.nonce, Some("random-nonce-67890".to_string()));
    /// ```
    #[must_use]
    pub fn with_nonce(mut self, nonce: impl Into<String>) -> Self {
        self.nonce = Some(nonce.into());
        self
    }

    /// Add a constraint for credential claim validation.
    ///
    /// Constraints become field requirements in the `presentation_definition`,
    /// requiring specific claim values in the presented credential.
    pub fn add_constraint(&mut self, key: impl Into<String>, value: serde_json::Value) {
        self.constraints
            .get_or_insert_with(HashMap::new)
            .insert(key.into(), value);
    }

    /// Set multiple constraints for credential claim validation.
    #[must_use]
    pub fn with_constraints(mut self, constraints: HashMap<String, serde_json::Value>) -> Self {
        self.constraints = Some(constraints);
        self
    }

    /// Check if this request requires a credential chain (has parent
    /// credentials).
    #[must_use]
    pub const fn has_chain(&self) -> bool {
        self.credential_types.len() > 1
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new() {
        let request = CredentialPolicyRequest::new("LastID.Base");
        assert_eq!(request.credential_type, "LastID.Base");
        assert_eq!(request.credential_types, vec!["LastID.Base"]);
        assert!(request.callback_url.is_none());
        assert!(request.constraints.is_none());
    }

    #[test]
    fn test_with_callback() {
        let request =
            CredentialPolicyRequest::new("LastID.Base").with_callback("https://example.com");
        assert_eq!(request.callback_url, Some("https://example.com".into()));
    }

    #[test]
    fn test_add_constraint() {
        let mut request = CredentialPolicyRequest::new("LastID.VerifiedEmail");
        request.add_constraint("email_pattern", serde_json::json!(".*@example.com"));

        let constraints = request.constraints.unwrap();
        assert_eq!(
            constraints.get("email_pattern"),
            Some(&serde_json::json!(".*@example.com"))
        );
    }

    #[test]
    fn test_serialize() {
        let request = CredentialPolicyRequest::new("LastID.Base")
            .with_callback("https://example.com/callback");

        let json = serde_json::to_value(&request).unwrap();
        assert_eq!(json["credentialType"], "LastID.Base");
        assert_eq!(json["callbackUrl"], "https://example.com/callback");
    }

    #[test]
    fn test_serialize_omits_none() {
        let request = CredentialPolicyRequest::new("LastID.Base");
        let json = serde_json::to_string(&request).unwrap();

        // Should not contain null values for optional fields
        assert!(!json.contains("callbackUrl"));
        assert!(!json.contains("constraints"));
        // Single-element chain should not serialize credentialTypes
        assert!(!json.contains("credentialTypes"));
    }

    // Credential chain tests (FR-007, FR-008)
    mod credential_chain_tests {
        use super::*;
        use crate::types::credential_request::credential_chain::{
            get_credential_chain, get_parent_credential, is_root_credential, types,
        };

        #[test]
        fn test_base_is_root() {
            assert!(is_root_credential(types::BASE));
            assert!(!is_root_credential(types::PERSONA));
            assert!(!is_root_credential(types::VERIFIED_EMAIL));
        }

        #[test]
        fn test_base_chain() {
            let chain = get_credential_chain(types::BASE);
            assert_eq!(chain, vec!["LastID.Base"]);
        }

        #[test]
        fn test_persona_chain() {
            let chain = get_credential_chain(types::PERSONA);
            assert_eq!(chain, vec!["LastID.Base", "LastID.Persona"]);
        }

        #[test]
        fn test_verified_email_chain() {
            // VerifiedEmail is a direct child of Base
            let chain = get_credential_chain(types::VERIFIED_EMAIL);
            assert_eq!(chain, vec!["LastID.Base", "LastID.VerifiedEmail"]);
        }

        #[test]
        fn test_verified_phone_chain() {
            // VerifiedPhone is a direct child of Base
            let chain = get_credential_chain(types::VERIFIED_PHONE);
            assert_eq!(chain, vec!["LastID.Base", "LastID.VerifiedPhone"]);
        }

        #[test]
        fn test_verified_persona_chain() {
            // VerifiedPersona is a direct child of Base
            let chain = get_credential_chain(types::VERIFIED_PERSONA);
            assert_eq!(chain, vec!["LastID.Base", "LastID.VerifiedPersona"]);
        }

        #[test]
        fn test_employment_chain() {
            // Employment is a child of Persona
            let chain = get_credential_chain(types::EMPLOYMENT);
            assert_eq!(
                chain,
                vec!["LastID.Base", "LastID.Persona", "LastID.Employment"]
            );
        }

        #[test]
        fn test_age_proof_chain() {
            // AgeProof is a child of VerifiedPersona
            let chain = get_credential_chain(types::AGE_PROOF);
            assert_eq!(
                chain,
                vec!["LastID.Base", "LastID.VerifiedPersona", "LastID.AgeProof"]
            );
        }

        #[test]
        fn test_trust_chain() {
            let chain = get_credential_chain(types::TRUST);
            assert_eq!(chain, vec!["LastID.Base", "LastID.Trust"]);
        }

        #[test]
        fn test_unknown_credential_type() {
            let chain = get_credential_chain("Unknown.Type");
            assert_eq!(chain, vec!["Unknown.Type"]);
        }

        #[test]
        fn test_parent_credentials() {
            assert_eq!(get_parent_credential(types::BASE), None);
            assert_eq!(get_parent_credential(types::PERSONA), Some(types::BASE));
            assert_eq!(get_parent_credential(types::TRUST), Some(types::BASE));
            // VerifiedEmail is direct child of Base
            assert_eq!(
                get_parent_credential(types::VERIFIED_EMAIL),
                Some(types::BASE)
            );
            // VerifiedPersona is direct child of Base
            assert_eq!(
                get_parent_credential(types::VERIFIED_PERSONA),
                Some(types::BASE)
            );
            // Employment is child of Persona
            assert_eq!(
                get_parent_credential(types::EMPLOYMENT),
                Some(types::PERSONA)
            );
            // AgeProof is child of VerifiedPersona
            assert_eq!(
                get_parent_credential(types::AGE_PROOF),
                Some(types::VERIFIED_PERSONA)
            );
        }

        #[test]
        fn test_with_chain_creates_full_chain() {
            // VerifiedEmail is direct child of Base, so chain is [Base, VerifiedEmail]
            let request = CredentialPolicyRequest::with_chain("LastID.VerifiedEmail");
            assert_eq!(request.credential_type, "LastID.VerifiedEmail");
            assert_eq!(
                request.credential_types,
                vec!["LastID.Base", "LastID.VerifiedEmail"]
            );
            assert!(request.has_chain());
        }

        #[test]
        fn test_with_chain_employment() {
            // Employment requires Base -> Persona -> Employment
            let request = CredentialPolicyRequest::with_chain("LastID.Employment");
            assert_eq!(request.credential_type, "LastID.Employment");
            assert_eq!(
                request.credential_types,
                vec!["LastID.Base", "LastID.Persona", "LastID.Employment"]
            );
            assert!(request.has_chain());
        }

        #[test]
        fn test_with_chain_base_no_chain() {
            let request = CredentialPolicyRequest::with_chain("LastID.Base");
            assert_eq!(request.credential_type, "LastID.Base");
            assert_eq!(request.credential_types, vec!["LastID.Base"]);
            assert!(!request.has_chain());
        }

        #[test]
        fn test_serialize_with_chain() {
            // Employment has a 3-element chain for good serialization test
            let request = CredentialPolicyRequest::with_chain("LastID.Employment")
                .with_callback("https://example.com/callback");

            let json = serde_json::to_value(&request).unwrap();
            assert_eq!(json["credentialType"], "LastID.Employment");
            assert_eq!(json["callbackUrl"], "https://example.com/callback");
            // Chain should be serialized for non-root credentials
            assert_eq!(
                json["credentialTypes"],
                serde_json::json!(["LastID.Base", "LastID.Persona", "LastID.Employment"])
            );
        }
    }
}
