//! Age proof credential policy builder.
//!
//! Requests age verification credentials without revealing the actual date of
//! birth.

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};

/// Age comparison operators for age proof policies.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum AgeComparison {
    /// User must be at least this age (>=)
    AtLeast,
    /// User must be over this age (>)
    Over,
    /// User must be under this age (<)
    Under,
    /// User must be at most this age (<=)
    AtMost,
    /// User must be exactly this age
    Exactly,
    /// User must be within an age range
    Between,
}

impl AgeComparison {
    /// Get the comparison as a string for the API.
    #[must_use]
    pub const fn as_str(&self) -> &'static str {
        match self {
            Self::AtLeast => "atLeast",
            Self::Over => "over",
            Self::Under => "under",
            Self::AtMost => "atMost",
            Self::Exactly => "exactly",
            Self::Between => "between",
        }
    }
}

/// Policy builder for age proof credentials.
///
/// This credential type allows verification of age without revealing
/// the user's actual date of birth.
///
/// # Type-State Pattern
///
/// - `AgeProofPolicy<Incomplete>` - Initial state
/// - `AgeProofPolicy<Complete>` - Ready to build
///
/// # Example
///
/// ```rust,no_run
/// use lastid_sdk::{AgeComparison, AgeProofPolicy, PolicyBuilder};
///
/// // Verify user is at least 21
/// let policy = AgeProofPolicy::new()
///     .at_least(21)
///     .with_callback("https://example.com/callback");
///
/// let request = policy.to_request().unwrap();
/// ```
#[cfg(feature = "age-proof-policy")]
pub struct AgeProofPolicy<State = Incomplete> {
    callback_url: Option<String>,
    comparison: Option<AgeComparison>,
    age: Option<u8>,
    max_age: Option<u8>, // For range comparisons
    _marker: PhantomData<State>,
}

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

#[cfg(feature = "age-proof-policy")]
impl AgeProofPolicy<Incomplete> {
    /// Create a new age proof credential policy builder.
    #[must_use]
    pub const fn new() -> Self {
        Self {
            callback_url: None,
            comparison: None,
            age: None,
            max_age: None,
            _marker: PhantomData,
        }
    }

    /// Require user to be at least the specified age (>=).
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use lastid_sdk::AgeProofPolicy;
    ///
    /// // Verify user is at least 21 years old
    /// let policy = AgeProofPolicy::new()
    ///     .at_least(21)
    ///     .with_callback("https://example.com/callback");
    /// ```
    #[must_use]
    pub const fn at_least(mut self, age: u8) -> Self {
        self.comparison = Some(AgeComparison::AtLeast);
        self.age = Some(age);
        self
    }

    /// Require user to be at least the specified age (>=).
    ///
    /// This is an alias for [`at_least`](Self::at_least) that matches the
    /// constraint naming convention used in other policies.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use lastid_sdk::AgeProofPolicy;
    ///
    /// // Verify user is at least 18 years old
    /// let policy = AgeProofPolicy::new()
    ///     .with_minimum_age(18)
    ///     .with_callback("https://example.com/callback");
    /// ```
    ///
    /// # Errors
    ///
    /// Returns `PolicyValidation` error at build time if age is 0 or > 150.
    #[must_use]
    pub const fn with_minimum_age(self, age: u8) -> Self {
        self.at_least(age)
    }

    /// Require user to be over the specified age (>).
    #[must_use]
    pub const fn over(mut self, age: u8) -> Self {
        self.comparison = Some(AgeComparison::Over);
        self.age = Some(age);
        self
    }

    /// Require user to be under the specified age (<).
    #[must_use]
    pub const fn under(mut self, age: u8) -> Self {
        self.comparison = Some(AgeComparison::Under);
        self.age = Some(age);
        self
    }

    /// Require user to be at most the specified age (<=).
    #[must_use]
    pub const fn at_most(mut self, age: u8) -> Self {
        self.comparison = Some(AgeComparison::AtMost);
        self.age = Some(age);
        self
    }

    /// Require user to be exactly the specified age.
    #[must_use]
    pub const fn exactly(mut self, age: u8) -> Self {
        self.comparison = Some(AgeComparison::Exactly);
        self.age = Some(age);
        self
    }

    /// Require user to be within the specified age range (inclusive).
    #[must_use]
    pub const fn between(mut self, min_age: u8, max_age: u8) -> Self {
        self.comparison = Some(AgeComparison::Between);
        self.age = Some(min_age);
        self.max_age = Some(max_age);
        self
    }

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

#[cfg(feature = "age-proof-policy")]
impl PolicyBuilder for AgeProofPolicy<Complete> {
    fn credential_type(&self) -> &'static str {
        "LastID.AgeProof"
    }

    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 age constraint is set
        if self.comparison.is_none() || self.age.is_none() {
            return Err(LastIDError::policy_validation(
                "Age proof policy requires an age constraint (use at_least(), over(), etc.)",
            ));
        }

        // Validate age value
        if let Some(age) = self.age {
            CredentialConstraint::validate_minimum_age(age)?;
        }

        // Validate range comparison
        if self.comparison == Some(AgeComparison::Between) {
            match (self.age, self.max_age) {
                (Some(min), Some(max)) if min >= max => {
                    return Err(LastIDError::policy_validation(
                        "Age range minimum must be less than maximum",
                    ));
                }
                (_, None) => {
                    return Err(LastIDError::policy_validation(
                        "Age range requires both minimum and maximum age",
                    ));
                }
                _ => {}
            }
        }

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

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

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

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

        let constraints = Some(constraints_map);

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

#[cfg(feature = "age-proof-policy")]
impl Clone for AgeProofPolicy<Complete> {
    fn clone(&self) -> Self {
        Self {
            callback_url: self.callback_url.clone(),
            comparison: self.comparison,
            age: self.age,
            max_age: self.max_age,
            _marker: PhantomData,
        }
    }
}

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

    #[test]
    fn test_age_proof_at_least() {
        let policy = AgeProofPolicy::new()
            .at_least(21)
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        assert_eq!(request.credential_type, "LastID.AgeProof");

        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("comparison").unwrap(), "atLeast");
        assert_eq!(constraints.get("age").unwrap(), 21);
    }

    #[test]
    fn test_age_proof_over() {
        let policy = AgeProofPolicy::new()
            .over(18)
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("comparison").unwrap(), "over");
        assert_eq!(constraints.get("age").unwrap(), 18);
    }

    #[test]
    fn test_age_proof_between() {
        let policy = AgeProofPolicy::new()
            .between(18, 65)
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("comparison").unwrap(), "between");
        assert_eq!(constraints.get("age").unwrap(), 18);
        assert_eq!(constraints.get("maxAge").unwrap(), 65);
    }

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

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

    #[test]
    fn test_age_proof_invalid_range() {
        let policy = AgeProofPolicy::new()
            .between(65, 18) // Invalid: min >= max
            .with_callback("https://example.com");

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

    #[test]
    fn test_age_comparison_as_str() {
        assert_eq!(AgeComparison::AtLeast.as_str(), "atLeast");
        assert_eq!(AgeComparison::Over.as_str(), "over");
        assert_eq!(AgeComparison::Under.as_str(), "under");
        assert_eq!(AgeComparison::AtMost.as_str(), "atMost");
        assert_eq!(AgeComparison::Exactly.as_str(), "exactly");
        assert_eq!(AgeComparison::Between.as_str(), "between");
    }

    #[test]
    fn test_with_minimum_age() {
        let policy = AgeProofPolicy::new()
            .with_minimum_age(21)
            .with_callback("https://example.com");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("comparison").unwrap(), "atLeast");
        assert_eq!(constraints.get("age").unwrap(), 21);
    }

    #[test]
    fn test_invalid_minimum_age_zero() {
        let policy = AgeProofPolicy::new()
            .with_minimum_age(0)
            .with_callback("https://example.com");

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