//! Persona profile system for consistent, personality-driven data generation
//!
//! This module provides a system for generating mock data with specific "personalities"
//! that remain consistent over time. Each persona has a unique ID, domain, traits,
//! and a deterministic seed that ensures the same persona always generates the same
//! data patterns.

use crate::domains::{Domain, DomainGenerator};
use crate::Result;
use rand::rngs::StdRng;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, RwLock};

/// Persona profile defining a consistent data personality
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersonaProfile {
    /// Unique identifier for this persona (e.g., user_id, device_id, transaction_id)
    pub id: String,
    /// Business domain this persona belongs to
    pub domain: Domain,
    /// Trait name to value mappings (e.g., "spending_level" → "high", "account_type" → "premium")
    pub traits: HashMap<String, String>,
    /// Deterministic seed derived from persona ID and domain for consistency
    pub seed: u64,
    /// Narrative backstory explaining persona behavior and characteristics
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub backstory: Option<String>,
    /// Relationships to other personas
    /// Keys: relationship types ("owns_devices", "belongs_to_org", "has_users")
    /// Values: List of related persona IDs
    #[serde(default)]
    pub relationships: HashMap<String, Vec<String>>,
    /// Additional persona-specific metadata
    #[serde(default)]
    pub metadata: HashMap<String, Value>,
    /// Optional lifecycle state management
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lifecycle: Option<crate::persona_lifecycle::PersonaLifecycle>,
}

impl PersonaProfile {
    /// Create a new persona profile
    ///
    /// The seed is deterministically derived from the persona ID and domain,
    /// ensuring the same ID and domain always produce the same seed.
    pub fn new(id: String, domain: Domain) -> Self {
        let seed = Self::derive_seed(&id, domain);
        Self {
            id,
            domain,
            traits: HashMap::new(),
            seed,
            backstory: None,
            relationships: HashMap::new(),
            metadata: HashMap::new(),
            lifecycle: None,
        }
    }

    /// Create a persona with initial traits
    pub fn with_traits(id: String, domain: Domain, traits: HashMap<String, String>) -> Self {
        let mut persona = Self::new(id, domain);
        persona.traits = traits;
        persona
    }

    /// Set the persona's lifecycle
    pub fn set_lifecycle(&mut self, lifecycle: crate::persona_lifecycle::PersonaLifecycle) {
        self.lifecycle = Some(lifecycle);
    }

    /// Get the persona's lifecycle
    pub fn get_lifecycle(&self) -> Option<&crate::persona_lifecycle::PersonaLifecycle> {
        self.lifecycle.as_ref()
    }

    /// Get mutable reference to lifecycle
    pub fn get_lifecycle_mut(&mut self) -> Option<&mut crate::persona_lifecycle::PersonaLifecycle> {
        self.lifecycle.as_mut()
    }

    /// Update lifecycle state based on virtual clock time
    ///
    /// Checks if any transitions should occur based on elapsed time and conditions.
    pub fn update_lifecycle_state(&mut self, current_time: chrono::DateTime<chrono::Utc>) {
        if let Some(ref mut lifecycle) = self.lifecycle {
            if let Some((new_state, _rule)) = lifecycle.transition_if_elapsed(current_time) {
                lifecycle.transition_to(new_state, current_time);

                // Apply lifecycle effects to persona traits
                let effects = lifecycle.apply_lifecycle_effects();
                for (key, value) in effects {
                    self.set_trait(key, value);
                }
            }
        }
    }

    /// Derive a deterministic seed from persona ID and domain
    ///
    /// Uses a simple hash function to convert the ID and domain into a u64 seed.
    /// This ensures the same ID and domain always produce the same seed.
    fn derive_seed(id: &str, domain: Domain) -> u64 {
        use std::collections::hash_map::DefaultHasher;
        let mut hasher = DefaultHasher::new();
        id.hash(&mut hasher);
        domain.as_str().hash(&mut hasher);
        hasher.finish()
    }

    /// Add or update a trait
    pub fn set_trait(&mut self, name: String, value: String) {
        self.traits.insert(name, value);
    }

    /// Get a trait value
    pub fn get_trait(&self, name: &str) -> Option<&String> {
        self.traits.get(name)
    }

    /// Add metadata
    pub fn set_metadata(&mut self, key: String, value: Value) {
        self.metadata.insert(key, value);
    }

    /// Get metadata
    pub fn get_metadata(&self, key: &str) -> Option<&Value> {
        self.metadata.get(key)
    }

    /// Set the persona's backstory
    ///
    /// The backstory provides narrative context that explains the persona's
    /// behavior and characteristics, enabling coherent data generation.
    pub fn set_backstory(&mut self, backstory: String) {
        self.backstory = Some(backstory);
    }

    /// Get the persona's backstory
    pub fn get_backstory(&self) -> Option<&String> {
        self.backstory.as_ref()
    }

    /// Check if the persona has a backstory
    pub fn has_backstory(&self) -> bool {
        self.backstory.is_some()
    }

    /// Add a relationship to another persona
    ///
    /// # Arguments
    /// * `relationship_type` - Type of relationship (e.g., "owns_devices", "belongs_to_org", "has_users")
    /// * `related_persona_id` - ID of the related persona
    pub fn add_relationship(&mut self, relationship_type: String, related_persona_id: String) {
        self.relationships
            .entry(relationship_type)
            .or_default()
            .push(related_persona_id);
    }

    /// Get all relationships of a specific type
    ///
    /// Returns a list of persona IDs that have the specified relationship type.
    pub fn get_relationships(&self, relationship_type: &str) -> Option<&Vec<String>> {
        self.relationships.get(relationship_type)
    }

    /// Get all related personas for a specific relationship type
    ///
    /// Returns a cloned vector of persona IDs, or an empty vector if no relationships exist.
    pub fn get_related_personas(&self, relationship_type: &str) -> Vec<String> {
        self.relationships.get(relationship_type).cloned().unwrap_or_default()
    }

    /// Get all relationship types for this persona
    pub fn get_relationship_types(&self) -> Vec<String> {
        self.relationships.keys().cloned().collect()
    }

    /// Remove a specific relationship
    ///
    /// Removes the specified persona ID from the relationship type's list.
    /// Returns true if the relationship was found and removed.
    pub fn remove_relationship(
        &mut self,
        relationship_type: &str,
        related_persona_id: &str,
    ) -> bool {
        if let Some(related_ids) = self.relationships.get_mut(relationship_type) {
            if let Some(pos) = related_ids.iter().position(|id| id == related_persona_id) {
                related_ids.remove(pos);
                // Clean up empty relationship lists
                if related_ids.is_empty() {
                    self.relationships.remove(relationship_type);
                }
                return true;
            }
        }
        false
    }
}

/// Registry for managing persona profiles
///
/// Provides thread-safe access to persona profiles with in-memory storage
/// and optional persistence capabilities.
#[derive(Debug, Clone)]
pub struct PersonaRegistry {
    /// In-memory storage of personas keyed by their ID
    personas: Arc<RwLock<HashMap<String, PersonaProfile>>>,
    /// Default traits to apply to new personas
    default_traits: HashMap<String, String>,
    /// Graph structure for relationship management
    graph: Arc<crate::persona_graph::PersonaGraph>,
}

impl PersonaRegistry {
    /// Create a new persona registry
    pub fn new() -> Self {
        Self {
            personas: Arc::new(RwLock::new(HashMap::new())),
            default_traits: HashMap::new(),
            graph: Arc::new(crate::persona_graph::PersonaGraph::new()),
        }
    }

    /// Create a registry with default traits for new personas
    pub fn with_default_traits(default_traits: HashMap<String, String>) -> Self {
        Self {
            personas: Arc::new(RwLock::new(HashMap::new())),
            default_traits,
            graph: Arc::new(crate::persona_graph::PersonaGraph::new()),
        }
    }

    /// Get the persona graph
    pub fn graph(&self) -> Arc<crate::persona_graph::PersonaGraph> {
        Arc::clone(&self.graph)
    }

    /// Get or create a persona profile
    ///
    /// If a persona with the given ID exists, returns it. Otherwise, creates
    /// a new persona with the specified domain and applies default traits.
    pub fn get_or_create_persona(&self, id: String, domain: Domain) -> PersonaProfile {
        let personas = self.personas.read().unwrap();

        // Check if persona already exists
        if let Some(persona) = personas.get(&id) {
            return persona.clone();
        }
        drop(personas);

        // Create new persona with default traits
        let mut persona = PersonaProfile::new(id.clone(), domain);
        for (key, value) in &self.default_traits {
            persona.set_trait(key.clone(), value.clone());
        }

        // Store the new persona
        let mut personas = self.personas.write().unwrap();
        personas.insert(id.clone(), persona.clone());

        // Add to graph
        let entity_type = persona.domain.as_str().to_string();
        let graph_node = crate::persona_graph::PersonaNode::new(id.clone(), entity_type);
        self.graph.add_node(graph_node);

        persona
    }

    /// Get a persona by ID
    pub fn get_persona(&self, id: &str) -> Option<PersonaProfile> {
        let personas = self.personas.read().unwrap();
        personas.get(id).cloned()
    }

    /// Update persona traits
    pub fn update_persona(&self, id: &str, traits: HashMap<String, String>) -> Result<()> {
        let mut personas = self.personas.write().unwrap();
        if let Some(persona) = personas.get_mut(id) {
            for (key, value) in traits {
                persona.set_trait(key, value);
            }
            Ok(())
        } else {
            Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
        }
    }

    /// Update persona backstory
    ///
    /// Sets or updates the backstory for an existing persona.
    pub fn update_persona_backstory(&self, id: &str, backstory: String) -> Result<()> {
        let mut personas = self.personas.write().unwrap();
        if let Some(persona) = personas.get_mut(id) {
            persona.set_backstory(backstory);
            Ok(())
        } else {
            Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
        }
    }

    /// Update persona with full profile data
    ///
    /// Updates traits, backstory, and relationships for an existing persona.
    /// This is useful when you have a complete persona profile to apply.
    pub fn update_persona_full(
        &self,
        id: &str,
        traits: Option<HashMap<String, String>>,
        backstory: Option<String>,
        relationships: Option<HashMap<String, Vec<String>>>,
    ) -> Result<()> {
        let mut personas = self.personas.write().unwrap();
        if let Some(persona) = personas.get_mut(id) {
            if let Some(traits) = traits {
                for (key, value) in traits {
                    persona.set_trait(key, value);
                }
            }
            if let Some(backstory) = backstory {
                persona.set_backstory(backstory);
            }
            if let Some(relationships) = relationships {
                for (rel_type, related_ids) in relationships {
                    for related_id in related_ids {
                        persona.add_relationship(rel_type.clone(), related_id);
                    }
                }
            }
            Ok(())
        } else {
            Err(crate::Error::generic(format!("Persona with ID '{}' not found", id)))
        }
    }

    /// Remove a persona
    pub fn remove_persona(&self, id: &str) -> bool {
        let mut personas = self.personas.write().unwrap();
        personas.remove(id).is_some()
    }

    /// Get all persona IDs
    pub fn list_persona_ids(&self) -> Vec<String> {
        let personas = self.personas.read().unwrap();
        personas.keys().cloned().collect()
    }

    /// Clear all personas
    pub fn clear(&self) {
        let mut personas = self.personas.write().unwrap();
        personas.clear();
    }

    /// Get the number of registered personas
    pub fn count(&self) -> usize {
        let personas = self.personas.read().unwrap();
        personas.len()
    }

    /// Get all personas that have a relationship of the specified type with the given persona
    ///
    /// Returns a vector of persona profiles that are related to the specified persona.
    pub fn get_related_personas(
        &self,
        persona_id: &str,
        relationship_type: &str,
    ) -> Result<Vec<PersonaProfile>> {
        let personas = self.personas.read().unwrap();
        if let Some(persona) = personas.get(persona_id) {
            let related_ids = persona.get_related_personas(relationship_type);
            let mut related_personas = Vec::new();
            for related_id in related_ids {
                if let Some(related_persona) = personas.get(&related_id) {
                    related_personas.push(related_persona.clone());
                }
            }
            Ok(related_personas)
        } else {
            Err(crate::Error::generic(format!("Persona with ID '{}' not found", persona_id)))
        }
    }

    /// Find all personas that have a relationship pointing to the specified persona
    ///
    /// This performs a reverse lookup to find personas that reference the given persona.
    pub fn find_personas_with_relationship_to(
        &self,
        target_persona_id: &str,
        relationship_type: &str,
    ) -> Vec<PersonaProfile> {
        let personas = self.personas.read().unwrap();
        let mut result = Vec::new();

        for persona in personas.values() {
            if let Some(related_ids) = persona.get_relationships(relationship_type) {
                if related_ids.contains(&target_persona_id.to_string()) {
                    result.push(persona.clone());
                }
            }
        }

        result
    }

    /// Add a relationship between two personas
    ///
    /// Creates a relationship from `from_persona_id` to `to_persona_id` of the specified type.
    pub fn add_relationship(
        &self,
        from_persona_id: &str,
        relationship_type: String,
        to_persona_id: String,
    ) -> Result<()> {
        let mut personas = self.personas.write().unwrap();
        if let Some(persona) = personas.get_mut(from_persona_id) {
            persona.add_relationship(relationship_type.clone(), to_persona_id.clone());

            // Also add to graph
            self.graph
                .add_edge(from_persona_id.to_string(), to_persona_id, relationship_type);

            Ok(())
        } else {
            Err(crate::Error::generic(format!(
                "Persona with ID '{}' not found",
                from_persona_id
            )))
        }
    }

    /// Switch to a new persona and update all related personas in the graph
    ///
    /// This ensures coherent persona switching across related entities.
    /// When switching to a new root persona, all related personas (orders, payments, etc.)
    /// are also updated to maintain consistency.
    ///
    /// # Arguments
    /// * `root_persona_id` - The root persona ID to switch to (e.g., user ID)
    /// * `relationship_types` - Optional filter for relationship types to follow
    /// * `update_callback` - Optional callback to update each related persona
    ///
    /// # Returns
    /// Vector of persona IDs that were updated
    pub fn coherent_persona_switch<F>(
        &self,
        root_persona_id: &str,
        relationship_types: Option<&[String]>,
        update_callback: Option<F>,
    ) -> Result<Vec<String>>
    where
        F: Fn(&str, &mut PersonaProfile),
    {
        // Find all related personas using BFS traversal
        let related_ids = self.graph.find_related_bfs(root_persona_id, relationship_types, None);

        // Start with the root persona
        let mut updated_ids = vec![root_persona_id.to_string()];
        updated_ids.extend(related_ids);

        // Update each persona in the graph
        let mut personas = self.personas.write().unwrap();
        for persona_id in &updated_ids {
            if let Some(persona) = personas.get_mut(persona_id) {
                // Apply update callback if provided
                if let Some(ref callback) = update_callback {
                    callback(persona_id, persona);
                }
            }
        }

        Ok(updated_ids)
    }
}

impl Default for PersonaRegistry {
    fn default() -> Self {
        Self::new()
    }
}

/// Generator for creating data based on persona profiles
///
/// Uses the persona's seed and traits to generate consistent, domain-appropriate
/// data that reflects the persona's personality.
#[derive(Debug)]
pub struct PersonaGenerator {
    /// Domain generator for domain-specific data generation
    domain_generator: DomainGenerator,
}

impl PersonaGenerator {
    /// Create a new persona generator
    pub fn new(domain: Domain) -> Self {
        Self {
            domain_generator: DomainGenerator::new(domain),
        }
    }

    /// Generate data for a specific field type based on persona
    ///
    /// Uses the persona's seed to create a deterministic RNG, then generates
    /// domain-specific data that may be influenced by the persona's traits.
    pub fn generate_for_persona(
        &self,
        persona: &PersonaProfile,
        field_type: &str,
    ) -> Result<Value> {
        // Generate with default reality ratio (0.0 = fully synthetic)
        self.generate_for_persona_with_reality(persona, field_type, 0.0, None, None)
    }

    /// Generate data for a specific field type based on persona with reality awareness
    ///
    /// The reality ratio determines how much the generated data blends with recorded/real data:
    /// - 0.0-0.3: Purely synthetic (persona-generated)
    /// - 0.3-0.7: Blended with recorded snapshots
    /// - 0.7-1.0: Blended with upstream/real data
    ///
    /// # Arguments
    /// * `persona` - Persona profile to generate data for
    /// * `field_type` - Type of field to generate (e.g., "name", "email", "amount")
    /// * `reality_ratio` - Reality continuum ratio (0.0 = mock, 1.0 = real)
    /// * `recorded_data` - Optional recorded/snapshot data to blend with
    /// * `real_data` - Optional real/upstream data to blend with
    pub fn generate_for_persona_with_reality(
        &self,
        persona: &PersonaProfile,
        field_type: &str,
        reality_ratio: f64,
        recorded_data: Option<&Value>,
        real_data: Option<&Value>,
    ) -> Result<Value> {
        // Create a deterministic RNG from the persona's seed
        use rand::rngs::StdRng;
        use rand::SeedableRng;
        let mut rng = StdRng::seed_from_u64(persona.seed);

        // Generate base synthetic value using domain generator
        let mut synthetic_value = self.domain_generator.generate(field_type)?;

        // Apply persona traits to influence the generated value
        synthetic_value =
            self.apply_persona_traits(persona, field_type, synthetic_value, &mut rng)?;

        // Apply reality continuum blending based on ratio
        let reality_ratio = reality_ratio.clamp(0.0, 1.0);

        if reality_ratio < 0.3 {
            // Low reality: Purely synthetic
            Ok(synthetic_value)
        } else if reality_ratio < 0.7 {
            // Medium reality: Blend with recorded snapshots
            if let Some(recorded) = recorded_data {
                self.blend_values(&synthetic_value, recorded, reality_ratio)
            } else {
                // No recorded data available, use synthetic
                Ok(synthetic_value)
            }
        } else {
            // High reality: Blend with upstream/real data
            if let Some(real) = real_data {
                self.blend_values(&synthetic_value, real, reality_ratio)
            } else if let Some(recorded) = recorded_data {
                // Fallback to recorded if real not available
                self.blend_values(&synthetic_value, recorded, reality_ratio)
            } else {
                // No real or recorded data, use synthetic
                Ok(synthetic_value)
            }
        }
    }

    /// Blend two values based on reality ratio
    ///
    /// Simple blending strategy: weighted average for numbers, weighted selection for strings/booleans
    fn blend_values(&self, synthetic: &Value, other: &Value, ratio: f64) -> Result<Value> {
        match (synthetic, other) {
            // Both numbers - weighted average
            (Value::Number(syn_num), Value::Number(other_num)) => {
                if let (Some(syn_f64), Some(other_f64)) = (syn_num.as_f64(), other_num.as_f64()) {
                    // Blend: synthetic * (1 - ratio) + other * ratio
                    // But adjust ratio for medium reality (0.3-0.7) to favor recorded
                    let adjusted_ratio = if ratio < 0.7 {
                        // Medium reality: map 0.3-0.7 to 0.0-1.0 for recorded blending
                        (ratio - 0.3) / 0.4
                    } else {
                        // High reality: map 0.7-1.0 to 0.0-1.0 for real blending
                        (ratio - 0.7) / 0.3
                    };
                    let blended = syn_f64 * (1.0 - adjusted_ratio) + other_f64 * adjusted_ratio;
                    Ok(Value::Number(
                        serde_json::Number::from_f64(blended).unwrap_or(syn_num.clone()),
                    ))
                } else {
                    Ok(synthetic.clone())
                }
            }
            // Both strings - weighted selection
            (Value::String(_), Value::String(other_str)) => {
                let adjusted_ratio = if ratio < 0.7 {
                    (ratio - 0.3) / 0.4
                } else {
                    (ratio - 0.7) / 0.3
                };
                if adjusted_ratio >= 0.5 {
                    Ok(Value::String(other_str.clone()))
                } else {
                    Ok(synthetic.clone())
                }
            }
            // Both booleans - weighted selection
            (Value::Bool(_), Value::Bool(other_bool)) => {
                let adjusted_ratio = if ratio < 0.7 {
                    (ratio - 0.3) / 0.4
                } else {
                    (ratio - 0.7) / 0.3
                };
                if adjusted_ratio >= 0.5 {
                    Ok(Value::Bool(*other_bool))
                } else {
                    Ok(synthetic.clone())
                }
            }
            // Type mismatch - prefer other if ratio is high enough
            _ => {
                let adjusted_ratio = if ratio < 0.7 {
                    (ratio - 0.3) / 0.4
                } else {
                    (ratio - 0.7) / 0.3
                };
                if adjusted_ratio >= 0.5 {
                    Ok(other.clone())
                } else {
                    Ok(synthetic.clone())
                }
            }
        }
    }

    /// Generate traits from a persona's backstory
    ///
    /// Analyzes the backstory to extract or infer trait values that align
    /// with the narrative. This ensures traits are coherent with the backstory.
    pub fn generate_traits_from_backstory(
        &self,
        persona: &PersonaProfile,
    ) -> Result<HashMap<String, String>> {
        let mut inferred_traits = HashMap::new();

        // If no backstory exists, return empty traits
        let backstory = match persona.get_backstory() {
            Some(bs) => bs,
            None => return Ok(inferred_traits),
        };

        let backstory_lower = backstory.to_lowercase();

        // Domain-specific trait inference from backstory
        match persona.domain {
            Domain::Finance => {
                // Infer spending level from backstory keywords
                if backstory_lower.contains("high-spending")
                    || backstory_lower.contains("high spending")
                    || backstory_lower.contains("big spender")
                {
                    inferred_traits.insert("spending_level".to_string(), "high".to_string());
                } else if backstory_lower.contains("conservative")
                    || backstory_lower.contains("low spending")
                    || backstory_lower.contains("frugal")
                {
                    inferred_traits
                        .insert("spending_level".to_string(), "conservative".to_string());
                } else if backstory_lower.contains("moderate") {
                    inferred_traits.insert("spending_level".to_string(), "moderate".to_string());
                }

                // Infer account type
                if backstory_lower.contains("premium") {
                    inferred_traits.insert("account_type".to_string(), "premium".to_string());
                } else if backstory_lower.contains("business") {
                    inferred_traits.insert("account_type".to_string(), "business".to_string());
                } else if backstory_lower.contains("savings") {
                    inferred_traits.insert("account_type".to_string(), "savings".to_string());
                } else if backstory_lower.contains("checking") {
                    inferred_traits.insert("account_type".to_string(), "checking".to_string());
                }

                // Extract currency if mentioned
                let currencies = ["usd", "eur", "gbp", "jpy", "cny"];
                for currency in &currencies {
                    if backstory_lower.contains(currency) {
                        inferred_traits
                            .insert("preferred_currency".to_string(), currency.to_uppercase());
                        break;
                    }
                }

                // Infer account age
                if backstory_lower.contains("long-term") || backstory_lower.contains("long term") {
                    inferred_traits.insert("account_age".to_string(), "long_term".to_string());
                } else if backstory_lower.contains("established") {
                    inferred_traits.insert("account_age".to_string(), "established".to_string());
                } else if backstory_lower.contains("new") {
                    inferred_traits.insert("account_age".to_string(), "new".to_string());
                }
            }
            Domain::Ecommerce => {
                // Infer customer segment
                if backstory_lower.contains("vip") {
                    inferred_traits.insert("customer_segment".to_string(), "VIP".to_string());
                } else if backstory_lower.contains("new") {
                    inferred_traits.insert("customer_segment".to_string(), "new".to_string());
                } else {
                    inferred_traits.insert("customer_segment".to_string(), "regular".to_string());
                }

                // Infer purchase frequency
                if backstory_lower.contains("frequent") {
                    inferred_traits
                        .insert("purchase_frequency".to_string(), "frequent".to_string());
                } else if backstory_lower.contains("occasional") {
                    inferred_traits
                        .insert("purchase_frequency".to_string(), "occasional".to_string());
                } else if backstory_lower.contains("regular") {
                    inferred_traits.insert("purchase_frequency".to_string(), "regular".to_string());
                }

                // Extract category if mentioned
                let categories = ["electronics", "clothing", "books", "home", "sports"];
                for category in &categories {
                    if backstory_lower.contains(category) {
                        inferred_traits
                            .insert("preferred_category".to_string(), category.to_string());
                        break;
                    }
                }

                // Infer shipping preference
                if backstory_lower.contains("express") || backstory_lower.contains("overnight") {
                    inferred_traits.insert("preferred_shipping".to_string(), "express".to_string());
                } else if backstory_lower.contains("standard") {
                    inferred_traits
                        .insert("preferred_shipping".to_string(), "standard".to_string());
                }
            }
            Domain::Healthcare => {
                // Infer insurance type
                if backstory_lower.contains("private") {
                    inferred_traits.insert("insurance_type".to_string(), "private".to_string());
                } else if backstory_lower.contains("medicare") {
                    inferred_traits.insert("insurance_type".to_string(), "medicare".to_string());
                } else if backstory_lower.contains("medicaid") {
                    inferred_traits.insert("insurance_type".to_string(), "medicaid".to_string());
                } else if backstory_lower.contains("uninsured") {
                    inferred_traits.insert("insurance_type".to_string(), "uninsured".to_string());
                }

                // Extract blood type if mentioned
                let blood_types = ["a+", "a-", "b+", "b-", "ab+", "ab-", "o+", "o-"];
                for blood_type in &blood_types {
                    if backstory_lower.contains(blood_type) {
                        inferred_traits.insert("blood_type".to_string(), blood_type.to_uppercase());
                        break;
                    }
                }

                // Infer age group
                if backstory_lower.contains("pediatric") || backstory_lower.contains("child") {
                    inferred_traits.insert("age_group".to_string(), "pediatric".to_string());
                } else if backstory_lower.contains("senior") || backstory_lower.contains("elderly")
                {
                    inferred_traits.insert("age_group".to_string(), "senior".to_string());
                } else {
                    inferred_traits.insert("age_group".to_string(), "adult".to_string());
                }

                // Infer visit frequency
                if backstory_lower.contains("frequent") {
                    inferred_traits.insert("visit_frequency".to_string(), "frequent".to_string());
                } else if backstory_lower.contains("regular") {
                    inferred_traits.insert("visit_frequency".to_string(), "regular".to_string());
                } else if backstory_lower.contains("occasional") {
                    inferred_traits.insert("visit_frequency".to_string(), "occasional".to_string());
                } else if backstory_lower.contains("rare") {
                    inferred_traits.insert("visit_frequency".to_string(), "rare".to_string());
                }

                // Infer chronic conditions
                if backstory_lower.contains("multiple") || backstory_lower.contains("several") {
                    inferred_traits
                        .insert("chronic_conditions".to_string(), "multiple".to_string());
                } else if backstory_lower.contains("single") || backstory_lower.contains("one") {
                    inferred_traits.insert("chronic_conditions".to_string(), "single".to_string());
                } else if backstory_lower.contains("none")
                    || backstory_lower.contains("no conditions")
                {
                    inferred_traits.insert("chronic_conditions".to_string(), "none".to_string());
                }
            }
            _ => {
                // For other domains, minimal inference
            }
        }

        Ok(inferred_traits)
    }

    /// Apply persona traits to influence generated values
    ///
    /// Modifies the generated value based on persona traits. For example,
    /// a high-spending persona might generate larger transaction amounts.
    /// If the persona has a backstory, traits inferred from the backstory
    /// are also considered.
    fn apply_persona_traits(
        &self,
        persona: &PersonaProfile,
        field_type: &str,
        value: Value,
        _rng: &mut StdRng,
    ) -> Result<Value> {
        // If persona has a backstory but is missing traits, try to infer them
        let mut effective_persona = persona.clone();
        if persona.has_backstory() && persona.traits.is_empty() {
            if let Ok(inferred_traits) = self.generate_traits_from_backstory(persona) {
                for (key, val) in inferred_traits {
                    effective_persona.set_trait(key, val);
                }
            }
        }

        match effective_persona.domain {
            Domain::Finance => self.apply_finance_traits(&effective_persona, field_type, value),
            Domain::Ecommerce => self.apply_ecommerce_traits(&effective_persona, field_type, value),
            Domain::Healthcare => {
                self.apply_healthcare_traits(&effective_persona, field_type, value)
            }
            _ => Ok(value), // For other domains, return value as-is for now
        }
    }

    /// Apply finance-specific persona traits
    fn apply_finance_traits(
        &self,
        persona: &PersonaProfile,
        field_type: &str,
        value: Value,
    ) -> Result<Value> {
        match field_type {
            "amount" | "balance" | "transaction_amount" => {
                // Adjust amount based on spending level trait
                if let Some(spending_level) = persona.get_trait("spending_level") {
                    let multiplier = match spending_level.as_str() {
                        "high" => 2.0,
                        "moderate" => 1.0,
                        "conservative" | "low" => 0.5,
                        _ => 1.0,
                    };

                    if let Some(num) = value.as_f64() {
                        return Ok(Value::Number(
                            serde_json::Number::from_f64(num * multiplier)
                                .unwrap_or_else(|| serde_json::Number::from(0)),
                        ));
                    }
                }
                Ok(value)
            }
            "currency" => {
                // Use preferred currency if trait exists
                if let Some(currency) = persona.get_trait("preferred_currency") {
                    return Ok(Value::String(currency.clone()));
                }
                Ok(value)
            }
            "account_type" => {
                // Use account type trait if exists
                if let Some(account_type) = persona.get_trait("account_type") {
                    return Ok(Value::String(account_type.clone()));
                }
                Ok(value)
            }
            _ => Ok(value),
        }
    }

    /// Apply e-commerce-specific persona traits
    fn apply_ecommerce_traits(
        &self,
        persona: &PersonaProfile,
        field_type: &str,
        value: Value,
    ) -> Result<Value> {
        match field_type {
            "price" | "order_total" => {
                // Adjust price based on customer segment
                if let Some(segment) = persona.get_trait("customer_segment") {
                    let multiplier = match segment.as_str() {
                        "VIP" => 1.5,
                        "regular" => 1.0,
                        "new" => 0.7,
                        _ => 1.0,
                    };

                    if let Some(num) = value.as_f64() {
                        return Ok(Value::Number(
                            serde_json::Number::from_f64(num * multiplier)
                                .unwrap_or_else(|| serde_json::Number::from(0)),
                        ));
                    }
                }
                Ok(value)
            }
            "shipping_method" => {
                // Use preferred shipping method if trait exists
                if let Some(shipping) = persona.get_trait("preferred_shipping") {
                    return Ok(Value::String(shipping.clone()));
                }
                Ok(value)
            }
            _ => Ok(value),
        }
    }

    /// Apply healthcare-specific persona traits
    fn apply_healthcare_traits(
        &self,
        persona: &PersonaProfile,
        field_type: &str,
        value: Value,
    ) -> Result<Value> {
        match field_type {
            "insurance_type" => {
                // Use insurance type trait if exists
                if let Some(insurance) = persona.get_trait("insurance_type") {
                    return Ok(Value::String(insurance.clone()));
                }
                Ok(value)
            }
            "blood_type" => {
                // Use blood type trait if exists
                if let Some(blood_type) = persona.get_trait("blood_type") {
                    return Ok(Value::String(blood_type.clone()));
                }
                Ok(value)
            }
            _ => Ok(value),
        }
    }
}

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

    #[test]
    fn test_persona_profile_new() {
        let persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
        assert_eq!(persona.id, "user123");
        assert_eq!(persona.domain, Domain::Finance);
        assert!(persona.traits.is_empty());
        assert!(persona.seed > 0);
    }

    #[test]
    fn test_persona_profile_deterministic_seed() {
        let persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
        let persona2 = PersonaProfile::new("user123".to_string(), Domain::Finance);

        // Same ID and domain should produce same seed
        assert_eq!(persona1.seed, persona2.seed);
    }

    #[test]
    fn test_persona_profile_different_seeds() {
        let persona1 = PersonaProfile::new("user123".to_string(), Domain::Finance);
        let persona2 = PersonaProfile::new("user456".to_string(), Domain::Finance);

        // Different IDs should produce different seeds
        assert_ne!(persona1.seed, persona2.seed);
    }

    #[test]
    fn test_persona_profile_traits() {
        let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
        persona.set_trait("spending_level".to_string(), "high".to_string());

        assert_eq!(persona.get_trait("spending_level"), Some(&"high".to_string()));
        assert_eq!(persona.get_trait("nonexistent"), None);
    }

    #[test]
    fn test_persona_registry_get_or_create() {
        let registry = PersonaRegistry::new();

        let persona1 = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
        let persona2 = registry.get_or_create_persona("user123".to_string(), Domain::Finance);

        // Should return the same persona
        assert_eq!(persona1.id, persona2.id);
        assert_eq!(persona1.seed, persona2.seed);
    }

    #[test]
    fn test_persona_registry_default_traits() {
        let mut default_traits = HashMap::new();
        default_traits.insert("spending_level".to_string(), "high".to_string());

        let registry = PersonaRegistry::with_default_traits(default_traits);
        let persona = registry.get_or_create_persona("user123".to_string(), Domain::Finance);

        assert_eq!(persona.get_trait("spending_level"), Some(&"high".to_string()));
    }

    #[test]
    fn test_persona_registry_update() {
        let registry = PersonaRegistry::new();
        registry.get_or_create_persona("user123".to_string(), Domain::Finance);

        let mut traits = HashMap::new();
        traits.insert("spending_level".to_string(), "low".to_string());

        registry.update_persona("user123", traits).unwrap();

        let persona = registry.get_persona("user123").unwrap();
        assert_eq!(persona.get_trait("spending_level"), Some(&"low".to_string()));
    }

    #[test]
    fn test_persona_generator_finance_traits() {
        let generator = PersonaGenerator::new(Domain::Finance);
        let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
        persona.set_trait("spending_level".to_string(), "high".to_string());

        // Generate amount - should be influenced by high spending level
        let value = generator.generate_for_persona(&persona, "amount").unwrap();
        assert!(value.is_string() || value.is_number());
    }

    #[test]
    fn test_persona_generator_consistency() {
        let generator = PersonaGenerator::new(Domain::Finance);
        let persona = PersonaProfile::new("user123".to_string(), Domain::Finance);

        // Generate multiple values - should be consistent due to deterministic seed
        let value1 = generator.generate_for_persona(&persona, "amount").unwrap();
        let value2 = generator.generate_for_persona(&persona, "amount").unwrap();

        // Note: Due to how domain generator works, values might differ,
        // but the seed ensures the RNG state is consistent
        assert!(value1.is_string() || value1.is_number());
        assert!(value2.is_string() || value2.is_number());
    }

    #[test]
    fn test_persona_backstory() {
        let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);
        assert!(!persona.has_backstory());
        assert_eq!(persona.get_backstory(), None);

        persona
            .set_backstory("High-spending finance professional with premium account".to_string());
        assert!(persona.has_backstory());
        assert!(persona.get_backstory().is_some());
        assert!(persona.get_backstory().unwrap().contains("High-spending"));
    }

    #[test]
    fn test_persona_relationships() {
        let mut persona = PersonaProfile::new("user123".to_string(), Domain::Finance);

        // Add relationships
        persona.add_relationship("owns_devices".to_string(), "device1".to_string());
        persona.add_relationship("owns_devices".to_string(), "device2".to_string());
        persona.add_relationship("belongs_to_org".to_string(), "org1".to_string());

        // Test getting relationships
        let devices = persona.get_related_personas("owns_devices");
        assert_eq!(devices.len(), 2);
        assert!(devices.contains(&"device1".to_string()));
        assert!(devices.contains(&"device2".to_string()));

        let orgs = persona.get_related_personas("belongs_to_org");
        assert_eq!(orgs.len(), 1);
        assert_eq!(orgs[0], "org1");

        // Test relationship types
        let types = persona.get_relationship_types();
        assert_eq!(types.len(), 2);
        assert!(types.contains(&"owns_devices".to_string()));
        assert!(types.contains(&"belongs_to_org".to_string()));

        // Test removing relationship
        assert!(persona.remove_relationship("owns_devices", "device1"));
        let devices_after = persona.get_related_personas("owns_devices");
        assert_eq!(devices_after.len(), 1);
        assert_eq!(devices_after[0], "device2");
    }

    #[test]
    fn test_persona_registry_relationships() {
        let registry = PersonaRegistry::new();

        // Create personas
        let _user = registry.get_or_create_persona("user123".to_string(), Domain::Finance);
        let _device = registry.get_or_create_persona("device1".to_string(), Domain::Iot);
        let _org = registry.get_or_create_persona("org1".to_string(), Domain::General);

        // Add relationships
        registry
            .add_relationship("user123", "owns_devices".to_string(), "device1".to_string())
            .unwrap();
        registry
            .add_relationship("user123", "belongs_to_org".to_string(), "org1".to_string())
            .unwrap();

        // Test getting related personas
        let related_devices = registry.get_related_personas("user123", "owns_devices").unwrap();
        assert_eq!(related_devices.len(), 1);
        assert_eq!(related_devices[0].id, "device1");

        // Test reverse lookup
        let owners = registry.find_personas_with_relationship_to("device1", "owns_devices");
        assert_eq!(owners.len(), 1);
        assert_eq!(owners[0].id, "user123");
    }
}
