/*
    Appellation: agent <module>
    Contrib: @FL03
*/
use crate::core::rstmt::prelude::{HarmonicFunction, LPR, PitchMod, Triad};
use crate::ctx::ActorContext;
use crate::drivers::RawDriver;
use crate::mem::TopoLedger;
use crate::traits::{Actor, ActorExt};
use num_traits::{Float, FromPrimitive, NumAssign, ToPrimitive};
use rshyper::EdgeId;
use scsys::Id;
use std::time::Instant;

pub(crate) type Actions = std::collections::VecDeque<(LPR, usize)>;

pub(crate) type Decisions = std::collections::VecDeque<(LPR, bool)>;

pub(crate) type PatternSuccess = std::collections::HashMap<Vec<usize>, (usize, usize)>; // (successes, attempts)

/// Agent actors actively participate in the system by analyzing, learning,
/// and proposing transformations based on their understanding of the musical context.
/// They represent the primary mode of operation for virtual nodes.
#[derive(Clone, Debug)]
pub struct Agent<T = f32> {
    pub(crate) id: Id,
    /// Recent decisions made (transform, success)
    pub(crate) decisions: Decisions,
    /// Exploration rate (0.0 - 1.0)
    pub(crate) exploration_rate: T,
    /// Last transformation evaluation time
    pub(crate) last_evaluation: Option<Instant>,
    /// Learning rate (0.0 - 1.0)
    pub(crate) learning_rate: T,
    /// Pattern success tracking - maps patterns to success rate
    pub(crate) pattern_success: PatternSuccess, // (successes, attempts)
    /// Recent actions taken by this agent
    pub(crate) recent_actions: Actions, // (transform, timestamp)
    /// Whether this agent has been initialized
    pub(crate) initialized: bool,
}

impl<T> Agent<T> {
    /// Create a new agent actor
    pub fn new() -> Self
    where
        T: FromPrimitive,
    {
        let id: Id;
        #[cfg(feature = "rand")]
        {
            id = Id::<u128>::random().map(|id| id as usize);
        }
        #[cfg(not(feature = "rand"))]
        {
            id = Id::atomic();
        }
        Agent {
            id,
            pattern_success: PatternSuccess::new(),
            recent_actions: Actions::with_capacity(50),
            decisions: Decisions::with_capacity(100),
            exploration_rate: T::from_f32(0.3).unwrap(),
            last_evaluation: None,
            learning_rate: T::from_f32(0.2).unwrap(),
            initialized: false,
        }
    }
    /// returns a copy of the actors id
    pub const fn id(&self) -> Id {
        self.id
    }
    /// returns a reference to the agent's decisions
    pub const fn decisions(&self) -> &Decisions {
        &self.decisions
    }
    /// returns a mutable reference to the agent's decisions
    pub const fn decisions_mut(&mut self) -> &mut Decisions {
        &mut self.decisions
    }
    /// returns a copy of the exploration rate
    pub const fn exploration_rate(&self) -> &T {
        &self.exploration_rate
    }
    /// returns a mutable reference to the agent's exploration rate
    pub const fn exploration_rate_mut(&mut self) -> &mut T {
        &mut self.exploration_rate
    }
    /// returns true if the agent has been initialized
    pub const fn is_initialized(&self) -> bool {
        self.initialized
    }
    /// returns true if the agent hasn't been initialized
    pub const fn is_uninitialized(&self) -> bool {
        !self.initialized
    }
    /// returns a reference to the agent's last evaluation time
    pub fn last_evaluation(&self) -> Option<&Instant> {
        self.last_evaluation.as_ref()
    }
    /// returns a mutable reference to the agent's last evaluation time
    pub fn last_evaluation_mut(&mut self) -> &mut Option<Instant> {
        &mut self.last_evaluation
    }
    /// returns a copy of the learning rate
    pub const fn learning_rate(&self) -> &T {
        &self.learning_rate
    }
    /// returns a mutable reference to the agent's learning rate
    pub const fn learning_rate_mut(&mut self) -> &mut T {
        &mut self.learning_rate
    }
    /// returns a reference to the agent's pattern success tracking
    pub const fn pattern_success(&self) -> &PatternSuccess {
        &self.pattern_success
    }
    /// returns a mutable reference to the agent's pattern success tracking
    pub const fn pattern_success_mut(&mut self) -> &mut PatternSuccess {
        &mut self.pattern_success
    }
    /// returns a reference to the agent's recent actions
    pub const fn recent_actions(&self) -> &Actions {
        &self.recent_actions
    }
    /// returns a mutable reference to the agent's recent actions
    pub const fn recent_actions_mut(&mut self) -> &mut Actions {
        &mut self.recent_actions
    }
    /// update the current id and return a mutable reference to the agent
    pub fn set_id(&mut self, id: Id) -> &mut Self {
        self.id = id;
        self
    }
    /// update the decisions and return a mutable reference to the agent
    pub fn set_decisions(&mut self, decisions: Decisions) -> &mut Self {
        self.decisions = decisions;
        self
    }
    /// update the current exploration rate and return a mutable reference to the agent
    pub fn set_exploration_rate(&mut self, rate: T) -> &mut Self
    where
        T: Float,
    {
        self.exploration_rate = rate.max(T::zero()).min(T::one());
        self
    }
    /// update the last evaluation time and return a mutable reference to the agent
    pub fn set_last_evaluation(&mut self, time: Instant) -> &mut Self {
        self.last_evaluation = Some(time);
        self
    }
    /// update the current learning rate and return a mutable reference to the agent
    pub fn set_learning_rate(&mut self, rate: T) -> &mut Self
    where
        T: Float,
    {
        self.learning_rate = rate.max(T::zero()).min(T::one());
        self
    }
    /// update the pattern success tracking and return a mutable reference to the agent
    pub fn set_pattern_success(&mut self, success: PatternSuccess) -> &mut Self {
        self.pattern_success = success;
        self
    }
    /// update the recent actions and return a mutable reference to the agent
    pub fn set_recent_actions(&mut self, actions: Actions) -> &mut Self {
        self.recent_actions = actions;
        self
    }
    /// consumes the current instance and returns another with the given decisions
    pub fn with_decisions(self, decisions: Decisions) -> Self {
        Self { decisions, ..self }
    }
    /// consumes the current instance and returns another with the given exploration rate
    pub fn with_exploration_rate(self, rate: T) -> Self
    where
        T: Float,
    {
        Self {
            exploration_rate: rate.max(T::zero()).min(T::one()),
            ..self
        }
    }
    /// consumes the current instance and returns another with the given id
    pub fn with_id(self, id: Id) -> Self {
        Self { id, ..self }
    }
    /// consumes the current instance and returns another with the given last evaluation time
    pub fn with_last_evaluation(self, time: Instant) -> Self {
        Self {
            last_evaluation: Some(time),
            ..self
        }
    }
    /// consumes the current instance and returns another with the given learning rate
    pub fn with_learning_rate(self, rate: T) -> Self
    where
        T: Float,
    {
        Self {
            learning_rate: rate.max(T::zero()).min(T::one()),
            ..self
        }
    }
    /// consumes the current instance and returns another with the given pattern success tracking
    pub fn with_pattern_success(self, success: PatternSuccess) -> Self {
        Self {
            pattern_success: success,
            ..self
        }
    }
    /// consumes the current instance and returns another with the given recent actions
    pub fn with_recent_actions(self, actions: Actions) -> Self {
        Self {
            recent_actions: actions,
            ..self
        }
    }
    /// Get the count of total decisions made
    pub fn total_decisions(&self) -> usize {
        self.decisions().len()
    }
    /// Get the count of successful decisions
    pub fn totel_successful_decisions(&self) -> usize {
        self.decisions()
            .iter()
            .filter(|(_, success)| *success)
            .count()
    }
    #[deprecated(since = "v0.0.3", note = "use `total_successful_decisions` instead")]
    pub fn successful_decisions(&self) -> usize {
        self.decisions
            .iter()
            .filter(|(_, success)| *success)
            .count()
    }
}

impl<T> Agent<T>
where
    T: FromPrimitive,
{
    /// Calculate how well a transformation aligns with recognized patterns
    pub(crate) fn calculate_pattern_alignment(&self, transform: LPR, memory: &TopoLedger<T>) -> T
    where
        T: Float,
    {
        // Get recent transforms from memory
        let recent_transforms = memory.recent_transformations(5);

        if recent_transforms.is_empty() {
            return T::from_usize(2).unwrap().recip(); // Neutral if no history
        }

        // Convert transform to usize for pattern matching
        let transform_id = match transform {
            LPR::Leading => 0,
            LPR::Parallel => 1,
            LPR::Relative => 2,
        };

        // Check if adding this transform would complete a known pattern
        let mut best_match = T::zero();

        for pattern in memory.patterns() {
            if pattern.sequence().is_empty() {
                continue;
            }

            // Check if recent transforms + new transform match pattern
            let mut potential_sequence = recent_transforms.clone();
            potential_sequence.push(transform_id);

            // Check if this is a prefix of the pattern
            let mut match_length = 0_usize;
            for (i, &t) in potential_sequence.iter().enumerate() {
                if i < pattern.sequence_len() && t == pattern[i] {
                    match_length += 1;
                } else {
                    break;
                }
            }

            // Calculate match quality
            let match_quality = if !pattern.is_empty() {
                T::from_usize(match_length).unwrap()
                    / T::from_usize(pattern.sequence_len()).unwrap()
            } else {
                T::zero()
            };

            // Weight by pattern importance
            let pattern_score = match_quality * *pattern.importance();
            best_match = best_match.max(pattern_score);
        }

        best_match
    }
    /// Calculate success history for a transform type
    pub(crate) fn calculate_success_history(&self, transform: LPR) -> T
    where
        T: core::ops::Div<T, Output = T>,
    {
        let transform_decisions = self
            .decisions
            .iter()
            .filter(|(t, _)| *t == transform)
            .collect::<Vec<_>>();

        if transform_decisions.is_empty() {
            return T::from_f32(0.5).unwrap(); // Neutral if no history
        }

        let success_count = transform_decisions
            .iter()
            .filter(|(_, success)| *success)
            .count();

        T::from_usize(success_count).unwrap() / T::from_usize(transform_decisions.len()).unwrap()
    }
    /// Calculate voice leading smoothness between triads
    pub(crate) fn calculate_voice_leading(&self, source: Triad, target: Triad) -> T
    where
        T: Float,
    {
        let source_notes = source.notes();
        let target_notes = target.notes();

        // Calculate total distance moved
        let mut total_distance = 0;

        // For each note in the source, find the closest note in target
        for s_note in source_notes {
            let min_distance = target_notes
                .iter()
                .map(|&t_note| {
                    let raw_distance = ((t_note as isize) - (s_note as isize)).pmod();
                    core::cmp::min(raw_distance, 12 - raw_distance) as usize
                })
                .min()
                .unwrap_or(6); // Default to tritone if no match

            total_distance += min_distance;
        }

        // Convert to 0.0-1.0 scale (0 = large movement, 1 = small movement)
        let max_possible_distance = 18; // Theoretical max for 3 notes

        T::one()
            - (T::from_usize(total_distance).unwrap()
                / T::from_usize(max_possible_distance).unwrap())
    }
    /// Choose the best transformation based on evaluation
    pub(crate) fn choose_transform<D>(&self, plant: &D, memory: &TopoLedger<T>) -> Option<LPR>
    where
        D: RawDriver<Triad>,
        T: Float + NumAssign + core::iter::Sum,
    {
        // Calculate scores for each transformation
        let mut transform_scores = vec![
            (
                LPR::Leading,
                self.evaluate_transform(LPR::Leading, plant, memory),
            ),
            (
                LPR::Parallel,
                self.evaluate_transform(LPR::Parallel, plant, memory),
            ),
            (
                LPR::Relative,
                self.evaluate_transform(LPR::Relative, plant, memory),
            ),
        ];

        // Sort by score (descending)
        transform_scores
            .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));

        // Random exploration chance
        #[cfg(feature = "rand")]
        let exploration_chance = T::from_f32(rand::random::<f32>()).unwrap();
        #[cfg(not(feature = "rand"))]
        let exploration_chance = T::from_f32(0.1).unwrap(); // 10% chance of random choice

        if exploration_chance < self.exploration_rate {
            // Choose randomly, weighted by scores
            let total_score = transform_scores.iter().map(|(_, score)| *score).sum::<T>();
            if total_score > T::zero() {
                #[cfg(feature = "rand")]
                let r = T::from_f32(rand::random::<f32>()).unwrap() * total_score;
                #[cfg(not(feature = "rand"))]
                let r = total_score; // Dummy value for non-random builds
                let mut cumulative = T::zero();

                for (transform, score) in &transform_scores {
                    cumulative += *score;
                    if r <= cumulative {
                        return Some(*transform);
                    }
                }
            }
        }

        // Otherwise choose the highest scoring transform
        transform_scores.first().map(|(transform, _)| *transform)
    }
    /// Evaluate a potential transformation
    pub(crate) fn evaluate_transform<D>(
        &self,
        transform: LPR,
        plant: &D,
        memory: &TopoLedger<T>,
    ) -> T
    where
        D: RawDriver<Triad>,
        T: Float + NumAssign,
    {
        // Get the current triad
        let current_triad = plant.headspace();

        // Calculate what the result would be
        let target_triad = current_triad.transform(transform);

        // Base evaluation on several factors
        let mut score = T::zero();

        // Factor 1: Stability of the target triad (0.0-1.0)
        let stability = memory.get_stability_for(&target_triad.notes());
        score += stability * T::from_f32(0.4).unwrap(); // 40% weight

        // Factor 2: Pattern alignment (0.0-1.0)
        let pattern_score = self.calculate_pattern_alignment(transform, memory);
        score += pattern_score * T::from_f32(0.3).unwrap(); // 30% weight

        // Factor 3: Voice leading smoothness (0.0-1.0)
        let voice_leading = self.calculate_voice_leading(*current_triad, target_triad);
        score += voice_leading * T::from_f32(0.2).unwrap(); // 20% weight

        // Factor 4: Success history for this transform type (0.0-1.0)
        let success_history = self.calculate_success_history(transform);
        score += success_history * T::from_f32(0.1).unwrap(); // 10% weight

        score
    }
    // Helper method to get recent transformations
    pub(crate) fn get_recent_transforms(
        &self,
        memory: &TopoLedger<T>,
        count: usize,
    ) -> Option<Vec<usize>>
    where
        T: Float + ToPrimitive,
    {
        if memory.count_features() < count {
            return None;
        }

        Some(memory.recent_transformations(count))
    }
    /// Record the outcome of a decision
    pub(crate) fn record_decision_outcome(&mut self, transform: LPR, success: bool) {
        self.decisions.push_back((transform, success));

        // Keep decision history from growing too large
        if self.decisions.len() > 100 {
            self.decisions.pop_front();
        }

        // Update pattern success tracking
        if !self.recent_actions.is_empty() {
            // Convert recent actions to pattern
            let pattern = self
                .recent_actions
                .iter()
                .map(|&(t, _)| t.into())
                .collect::<Vec<_>>();

            // Record success/failure
            let entry = self.pattern_success.entry(pattern).or_insert((0, 0));
            if success {
                entry.0 += 1; // Increment successes
            }
            entry.1 += 1; // Increment attempts
        }
    }
}

impl<T> Default for Agent<T>
where
    T: FromPrimitive,
{
    fn default() -> Self {
        Self::new()
    }
}

impl<D, T> Actor<D, T> for Agent<T>
where
    D: RawDriver<Triad>,
    T: core::iter::Sum + Float + FromPrimitive + NumAssign,
{
    fn kind(&self) -> &'static str {
        "Agent"
    }

    fn initialize(&mut self) -> crate::Result<()> {
        if !self.initialized {
            self.pattern_success.clear();
            self.recent_actions.clear();
            self.decisions.clear();
            self.initialized = true;
        }
        Ok(())
    }

    fn process_transform(
        &mut self,
        transform: LPR,
        plant: &mut D,
        memory: &mut TopoLedger<T>,
    ) -> crate::Result<bool> {
        // Record the action with current timestamp
        let timestamp = memory.current_epoch();
        self.recent_actions.push_back((transform, timestamp));

        // Keep action history from growing too large
        if self.recent_actions.len() > 50 {
            self.recent_actions.pop_front();
        }

        // Evaluate the proposed transformation
        let score = self.evaluate_transform(transform, plant, memory);

        // Set last evaluation time
        self.last_evaluation = Some(Instant::now());

        // Accept or reject based on score
        let threshold =
            T::from_f32(0.5).unwrap() - (self.learning_rate * T::from_f32(0.2).unwrap()); // Lower threshold when learning
        let accept = score >= threshold;

        // Record decision outcome (success based on acceptance)
        self.record_decision_outcome(transform, accept);

        Ok(accept)
    }

    fn process_message(
        &mut self,
        _source: EdgeId,
        message: &[u8],
        memory: &mut TopoLedger<T>,
    ) -> crate::Result<()> {
        // Basic message processing - just record it
        if let Ok(message_str) = std::str::from_utf8(message) {
            // Check if message contains a pattern we should learn
            if message_str.starts_with("PATTERN:") {
                if let Some(pattern_str) = message_str.strip_prefix("PATTERN:") {
                    // Parse pattern from string
                    let pattern: Vec<usize> = pattern_str
                        .split(',')
                        .filter_map(|s| s.trim().parse::<usize>().ok())
                        .collect();

                    if !pattern.is_empty() {
                        memory.record_pattern(&pattern);
                    }
                }
            }
        }

        Ok(())
    }

    fn on_activate(&mut self, _plant: &D, memory: &mut TopoLedger<T>) -> crate::Result<()> {
        // When activated, analyze existing patterns and prepare for decisions
        self.recent_actions.clear();
        self.last_evaluation = None;

        // Record initial triad in recent actions to establish baseline
        let timestamp = memory.current_epoch();
        let start_transform = LPR::default();
        self.recent_actions.push_back((start_transform, timestamp));

        Ok(())
    }

    fn on_deactivate(&mut self, _plant: &D, memory: &mut TopoLedger<T>) -> crate::Result<()> {
        // Before deactivation, record learned pattern effectiveness
        for (pattern, (successes, attempts)) in &self.pattern_success {
            if *attempts > 0 {
                let success_rate =
                    T::from_usize(*successes).unwrap() / T::from_usize(*attempts).unwrap();

                // Only record patterns with multiple attempts and good success rate
                if *attempts >= 3 && success_rate > T::from_f32(0.6).unwrap() {
                    memory.record_pattern_with_importance(pattern, success_rate);
                }
            }
        }

        Ok(())
    }

    fn resource_requirements(&self) -> (usize, usize) {
        // Agent uses more resources than Agent
        (150, 80) // (memory, compute)
    }

    fn allows_pattern_sharing(&self) -> bool {
        true // Agent allows pattern sharing
    }

    fn contextualize(&self, plant: &D, memory: &TopoLedger<T>) -> crate::Result<ActorContext<T>> {
        let triad = plant.headspace();

        // Calculate stability based on memory and decision history
        let memory_stability = memory.get_stability_for(&triad.notes());

        // Adjust stability based on decision success history
        let decision_success_rate = if self.decisions.is_empty() {
            T::from_usize(2).unwrap().recip() // Neutral if no decisions made
        } else {
            let success_count = self
                .decisions
                .iter()
                .filter(|(_, success)| *success)
                .count();

            T::from_usize(success_count).unwrap() / T::from_usize(self.decisions.len()).unwrap()
        };

        // Combined stability (70% memory, 30% decisions)
        let stability = (memory_stability * T::from_f32(0.7).unwrap())
            + (decision_success_rate * T::from_f32(0.3).unwrap());

        // Determine harmonic function based on root
        // Using C as tonic reference
        let harmonic_function = match triad.root().pmod() {
            0 => Some(HarmonicFunction::Tonic),        // C
            7 => Some(HarmonicFunction::Dominant),     // G
            5 => Some(HarmonicFunction::Subdominant),  // F
            9 => Some(HarmonicFunction::Submediant),   // A
            2 => Some(HarmonicFunction::Supertonic),   // D
            11 => Some(HarmonicFunction::Subdominant), // B (leading tone)
            _ => None,
        };

        // Generate semantic tags based on pattern recognition
        let mut semantic_tags = Vec::new();

        // Check for cadential patterns
        if let Some(recent) = self.get_recent_transforms(memory, 2) {
            if recent == vec![2, 1] {
                // Relative -> Parallel
                semantic_tags.push("Cadence".to_string());
            } else if recent == vec![0, 2] {
                // Leading -> Relative
                semantic_tags.push("Deceptive".to_string());
            }
        }

        // Add descriptor based on stability
        if stability > T::from_f32(0.8).unwrap() {
            semantic_tags.push("Stable".to_string());
        } else if stability < T::from_f32(0.4).unwrap() {
            semantic_tags.push("Unstable".to_string());
        }

        // Create context
        Ok(ActorContext {
            harmonic_function,
            metric_position: None, // Agents don't track metrics yet
            semantic_tags,
            stability,
        })
    }
}

impl<D, T> ActorExt<D, T> for Agent<T>
where
    D: RawDriver<Triad>,
    T: core::iter::Sum + Float + FromPrimitive + NumAssign,
{
    /// Propose a transformation based on current state
    fn propose_transformation(&self, plant: &D, memory: &TopoLedger<T>) -> Option<LPR> {
        self.choose_transform(plant, memory)
    }
}

impl<T> PartialEq<Agent<T>> for Agent<T> {
    fn eq(&self, other: &Agent<T>) -> bool {
        self.id() == other.id()
    }
}

impl<T> PartialEq<Agent<T>> for &Agent<T> {
    fn eq(&self, other: &Agent<T>) -> bool {
        self.id() == other.id()
    }
}

impl<T> PartialEq<Agent<T>> for &mut Agent<T> {
    fn eq(&self, other: &Agent<T>) -> bool {
        self.id() == other.id()
    }
}

impl<'a, T> PartialEq<&'a Agent<T>> for Agent<T> {
    fn eq(&self, other: &&'a Agent<T>) -> bool {
        self.id() == other.id()
    }
}

impl<'a, T> PartialEq<&'a mut Agent<T>> for Agent<T> {
    fn eq(&self, other: &&'a mut Agent<T>) -> bool {
        self.id() == other.id()
    }
}

impl<T> Eq for Agent<T> {}

impl<T> core::hash::Hash for Agent<T> {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        self.id().hash(state);
        self.recent_actions().hash(state);
        self.decisions().hash(state);
        // self.learning_rate().hash(state);
        // self.exploration_rate().hash(state);
        self.last_evaluation().hash(state);
        self.is_initialized().hash(state);
    }
}
