//! Unit tests for policy builders.
//!
//! These tests are synchronous and run on both native and WASM targets.
//! They test the type-state pattern and constraint validation for all policy types.

use lastid_sdk::policies::PolicyBuilder;
use lastid_sdk::BaseCredentialPolicy;

// =============================================================================
// BaseCredentialPolicy Tests
// =============================================================================

#[test]
fn test_base_policy_type_state() {
    // Can create incomplete policy
    let _incomplete = BaseCredentialPolicy::new();

    // Calling with_callback transitions to Complete state
    let complete = BaseCredentialPolicy::new().with_callback("https://example.com/callback");

    // Complete state can be converted to request
    let request = complete.to_request().unwrap();
    assert_eq!(request.credential_type, "LastID.Base");
    assert_eq!(
        request.callback_url.unwrap(),
        "https://example.com/callback"
    );
}

#[test]
fn test_base_policy_callback_validation() {
    // Valid HTTPS callback
    let policy = BaseCredentialPolicy::new().with_callback("https://example.com/callback");
    assert!(policy.to_request().is_ok());

    // Valid HTTP callback (allowed for dev)
    let policy = BaseCredentialPolicy::new().with_callback("http://localhost:3000/callback");
    assert!(policy.to_request().is_ok());

    // Invalid callback (no protocol)
    let policy = BaseCredentialPolicy::new().with_callback("example.com/callback");
    assert!(policy.to_request().is_err());
}

#[test]
fn test_base_policy_with_state_and_nonce() {
    let policy = BaseCredentialPolicy::new()
        .with_state("csrf-token-123")
        .with_nonce("binding-nonce-456")
        .with_callback("https://example.com/callback");

    let request = policy.to_request().unwrap();
    assert_eq!(request.state, Some("csrf-token-123".into()));
    assert_eq!(request.nonce, Some("binding-nonce-456".into()));
}

// =============================================================================
// PersonaPolicy Tests
// =============================================================================

#[cfg(feature = "persona-policy")]
mod persona_tests {
    use super::*;
    use lastid_sdk::{PersonaField, PersonaPolicy};

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

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

    #[test]
    fn test_persona_policy_with_fields() {
        let policy = PersonaPolicy::new()
            .with_fields(&[
                PersonaField::FullName,
                PersonaField::GivenName,
                PersonaField::Picture,
            ])
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        assert!(request.constraints.is_some());

        let constraints = request.constraints.unwrap();
        let fields = constraints.get("requestedFields").unwrap();
        assert_eq!(fields.as_array().unwrap().len(), 3);
    }

    #[test]
    fn test_persona_field_serialization() {
        assert_eq!(PersonaField::FullName.as_str(), "fullName");
        assert_eq!(PersonaField::GivenName.as_str(), "givenName");
        assert_eq!(PersonaField::FamilyName.as_str(), "familyName");
        assert_eq!(PersonaField::DateOfBirth.as_str(), "dateOfBirth");
        assert_eq!(PersonaField::Address.as_str(), "address");
        assert_eq!(PersonaField::Picture.as_str(), "picture");
    }
}

// =============================================================================
// VerifiedEmailPolicy Tests
// =============================================================================

#[cfg(feature = "verified-email-policy")]
mod verified_email_tests {
    use super::*;
    use lastid_sdk::VerifiedEmailPolicy;

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

        let request = policy.to_request().unwrap();
        assert_eq!(request.credential_type, "LastID.VerifiedEmail");
        assert!(request.constraints.is_none());
    }

    #[test]
    fn test_verified_email_with_domain_constraint() {
        let policy = VerifiedEmailPolicy::new()
            .with_domain_constraint("acme.com")
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("domainConstraint").unwrap(), "acme.com");
    }

    #[test]
    fn test_verified_email_invalid_domain() {
        // Domain should not contain @
        let policy = VerifiedEmailPolicy::new()
            .with_domain_constraint("user@example.com")
            .with_callback("https://example.com/callback");

        assert!(policy.to_request().is_err());
    }

    #[test]
    fn test_verified_email_empty_domain() {
        let policy = VerifiedEmailPolicy::new()
            .with_domain_constraint("")
            .with_callback("https://example.com/callback");

        assert!(policy.to_request().is_err());
    }
}

// =============================================================================
// VerifiedPhonePolicy Tests
// =============================================================================

#[cfg(feature = "verified-phone-policy")]
mod verified_phone_tests {
    use super::*;
    use lastid_sdk::VerifiedPhonePolicy;

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

        let request = policy.to_request().unwrap();
        assert_eq!(request.credential_type, "LastID.VerifiedPhone");
        assert!(request.constraints.is_none());
    }

    #[test]
    fn test_verified_phone_with_country_code() {
        let policy = VerifiedPhonePolicy::new()
            .with_country_code("+1")
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("countryCode").unwrap(), "+1");
    }

    #[test]
    fn test_verified_phone_invalid_country_code() {
        // Country code must start with +
        let policy = VerifiedPhonePolicy::new()
            .with_country_code("1")
            .with_callback("https://example.com/callback");

        assert!(policy.to_request().is_err());
    }

    #[test]
    fn test_verified_phone_short_country_code() {
        // Country code must have at least one digit
        let policy = VerifiedPhonePolicy::new()
            .with_country_code("+")
            .with_callback("https://example.com/callback");

        assert!(policy.to_request().is_err());
    }
}

// =============================================================================
// VerifiedPersonaPolicy Tests
// =============================================================================

#[cfg(feature = "verified-persona-policy")]
mod verified_persona_tests {
    use super::*;
    use lastid_sdk::{VerificationLevel, VerifiedPersonaField, VerifiedPersonaPolicy};

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

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

    #[test]
    fn test_verified_persona_with_level() {
        let policy = VerifiedPersonaPolicy::new()
            .with_verification_level(VerificationLevel::Enhanced)
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("verificationLevel").unwrap(), "enhanced");
    }

    #[test]
    fn test_verified_persona_with_fields() {
        let policy = VerifiedPersonaPolicy::new()
            .with_fields(&[
                VerifiedPersonaField::LegalName,
                VerifiedPersonaField::DateOfBirth,
            ])
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        let fields = constraints.get("requestedFields").unwrap();
        assert_eq!(fields.as_array().unwrap().len(), 2);
    }

    #[test]
    fn test_verification_level_serialization() {
        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_serialization() {
        assert_eq!(VerifiedPersonaField::LegalName.as_str(), "legalName");
        assert_eq!(VerifiedPersonaField::DateOfBirth.as_str(), "dateOfBirth");
        assert_eq!(VerifiedPersonaField::Nationality.as_str(), "nationality");
        assert_eq!(
            VerifiedPersonaField::VerifiedAddress.as_str(),
            "verifiedAddress"
        );
        assert_eq!(
            VerifiedPersonaField::DocumentPhoto.as_str(),
            "documentPhoto"
        );
        assert_eq!(
            VerifiedPersonaField::DocumentNumber.as_str(),
            "documentNumber"
        );
    }
}

// =============================================================================
// EmploymentPolicy Tests
// =============================================================================

#[cfg(feature = "employment-policy")]
mod employment_tests {
    use super::*;
    use lastid_sdk::{EmploymentField, EmploymentPolicy, EmploymentStatus};

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

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

    #[test]
    fn test_employment_policy_with_status() {
        let policy = EmploymentPolicy::new()
            .with_status(EmploymentStatus::Current)
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("employmentStatus").unwrap(), "current");
    }

    #[test]
    fn test_employment_policy_with_employer_domain() {
        let policy = EmploymentPolicy::new()
            .with_employer_domain("acme.com")
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("employerDomain").unwrap(), "acme.com");
    }

    #[test]
    fn test_employment_policy_with_fields() {
        let policy = EmploymentPolicy::new()
            .with_fields(&[
                EmploymentField::JobTitle,
                EmploymentField::Department,
                EmploymentField::StartDate,
            ])
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        let fields = constraints.get("requestedFields").unwrap();
        assert_eq!(fields.as_array().unwrap().len(), 3);
    }

    #[test]
    fn test_employment_status_serialization() {
        assert_eq!(EmploymentStatus::Current.as_str(), "current");
        assert_eq!(EmploymentStatus::Former.as_str(), "former");
        assert_eq!(EmploymentStatus::Any.as_str(), "any");
    }

    #[test]
    fn test_employment_field_serialization() {
        assert_eq!(EmploymentField::JobTitle.as_str(), "jobTitle");
        assert_eq!(EmploymentField::Department.as_str(), "department");
        assert_eq!(EmploymentField::StartDate.as_str(), "startDate");
        assert_eq!(EmploymentField::EndDate.as_str(), "endDate");
        assert_eq!(EmploymentField::EmployerName.as_str(), "employerName");
    }
}

// =============================================================================
// TrustPolicy Tests
// =============================================================================

#[cfg(feature = "trust-policy")]
mod trust_tests {
    use super::*;
    use lastid_sdk::{TrustLevel, TrustPolicy};

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

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

    #[test]
    fn test_trust_policy_with_level() {
        let policy = TrustPolicy::new()
            .with_minimum_level(TrustLevel::High)
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("minimumTrustLevel").unwrap(), "high");
    }

    #[test]
    fn test_trust_policy_with_issuer() {
        let policy = TrustPolicy::new()
            .with_trusted_issuer("did:lastid:issuer:trusted")
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        let issuers = constraints.get("trustedIssuers").unwrap();
        assert!(issuers
            .as_array()
            .unwrap()
            .contains(&serde_json::json!("did:lastid:issuer:trusted")));
    }

    #[test]
    fn test_trust_policy_mutual() {
        let policy = TrustPolicy::new()
            .require_mutual_trust()
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("requireMutual").unwrap(), true);
    }

    #[test]
    fn test_trust_policy_max_age() {
        let policy = TrustPolicy::new()
            .with_max_age_days(30) // 30 days
            .with_callback("https://example.com/callback");

        let request = policy.to_request().unwrap();
        let constraints = request.constraints.unwrap();
        assert_eq!(constraints.get("maxAgeDays").unwrap(), 30);
    }

    #[test]
    fn test_trust_level_serialization() {
        assert_eq!(TrustLevel::None.as_str(), "none");
        assert_eq!(TrustLevel::Basic.as_str(), "basic");
        assert_eq!(TrustLevel::Verified.as_str(), "verified");
        assert_eq!(TrustLevel::High.as_str(), "high");
        assert_eq!(TrustLevel::Ultimate.as_str(), "ultimate");
    }

    #[test]
    fn test_trust_level_ordering() {
        assert!(TrustLevel::None < TrustLevel::Basic);
        assert!(TrustLevel::Basic < TrustLevel::Verified);
        assert!(TrustLevel::Verified < TrustLevel::High);
        assert!(TrustLevel::High < TrustLevel::Ultimate);
    }
}

// =============================================================================
// AgeProofPolicy Tests
// =============================================================================

#[cfg(feature = "age-proof-policy")]
mod age_proof_tests {
    use super::*;
    use lastid_sdk::{AgeComparison, AgeProofPolicy};

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

        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/callback");

        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_under() {
        let policy = AgeProofPolicy::new()
            .under(65)
            .with_callback("https://example.com/callback");

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

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

        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_exactly() {
        let policy = AgeProofPolicy::new()
            .exactly(30)
            .with_callback("https://example.com/callback");

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

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

        assert!(policy.to_request().is_err());
    }

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

        assert!(policy.to_request().is_err());
    }

    #[test]
    fn test_age_proof_equal_range() {
        // min == max is invalid (use exactly() instead)
        let policy = AgeProofPolicy::new()
            .between(21, 21)
            .with_callback("https://example.com/callback");

        assert!(policy.to_request().is_err());
    }

    #[test]
    fn test_age_comparison_serialization() {
        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");
    }
}
