//! Condition audit data structures for rule-based strategy backtests.
//!
//! Two tiers:
//! - **Summary** (always-on): per-condition pass rates accumulated during the backtest
//!   and stored in `result_summary.condition_audit_summary`. No per-bar storage.
//! - **Detail** (opt-in via `RunConfig::audit_conditions`): per-bar `BarAudit` entries
//!   persisted to the `paper_run_condition_audit` table and downloadable via API.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

/// Per-condition evaluation result for one bar.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionAudit {
    /// JSON path within the rule, e.g. `"when.conditions[2]"`.
    pub path: String,
    /// Condition kind discriminator, e.g. `"compare"`, `"cross_over"`, `"position"`.
    pub kind: String,
    /// Optional human-readable annotation from the condition's `comment` field.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
    /// Whether the condition evaluated to `true`.
    pub result: bool,
    /// `true` when the condition returned `false` due to insufficient candle history
    /// (an `Expr` returning `None`) rather than a genuine logical false.
    pub insufficient_data: bool,
    /// Left-hand numeric value for binary conditions (Compare, CrossOver, CrossUnder,
    /// Rsi, Bollinger, EventCount). Formatted as a decimal string.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub left_value: Option<String>,
    /// Right-hand numeric value for binary conditions.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub right_value: Option<String>,
}

/// Audit record for a single candle bar, covering one rule's full condition tree.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BarAudit {
    /// Monotonically increasing bar index within the run (0-based).
    pub bar_index: u32,
    /// Candle close time, if available from `CandleRecord::open_time`.
    pub bar_time: DateTime<Utc>,
    /// Index of the rule within the strategy's `rules` array (0-based).
    pub rule_index: u8,
    /// Optional `comment` from the `Rule` struct.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rule_comment: Option<String>,
    /// Whether the rule's `when` condition evaluated to `true` (an order was placed).
    pub rule_fired: bool,
    /// Flat list of all conditions evaluated, in depth-first order.
    pub conditions: Vec<ConditionAudit>,
}

/// Pass-rate statistics for a single condition path across all bars.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionPassRate {
    pub path: String,
    pub kind: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
    pub true_count: u32,
    /// Fraction of bars where this condition was true (0.0–1.0).
    pub true_rate: f64,
    /// Bars where the condition returned false due to insufficient history.
    pub insufficient_data_count: u32,
}

/// Aggregate statistics for one rule across the entire backtest.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleAuditSummary {
    pub rule_index: u8,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rule_comment: Option<String>,
    pub times_fired: u32,
    pub fire_rate: f64,
    pub conditions: Vec<ConditionPassRate>,
}

/// Top-level summary stored in `result_summary.condition_audit_summary`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConditionAuditSummary {
    /// Number of candle closes evaluated by the strategy.
    pub total_bars: u32,
    pub rules: Vec<RuleAuditSummary>,
}

/// Shared audit log — interior-mutable so `generate_algo_orders(&self)` can append.
pub type AuditLog = Arc<Mutex<Vec<BarAudit>>>;

/// Construct a fresh, empty audit log.
pub fn new_audit_log() -> AuditLog {
    Arc::new(Mutex::new(Vec::new()))
}

/// Compute a `ConditionAuditSummary` by aggregating all `BarAudit` entries.
///
/// Groups by `(rule_index, condition_path)` and computes pass rates.
pub fn compute_summary(audits: &[BarAudit]) -> ConditionAuditSummary {
    use std::collections::BTreeMap;

    let total_bars = audits
        .iter()
        .map(|a| a.bar_index)
        .max()
        .map(|m| m + 1)
        .unwrap_or(0);

    // Collect per-rule stats.
    // Key: rule_index → (rule_comment, times_fired, per-path counters)
    let mut rule_map: BTreeMap<u8, (Option<String>, u32, BTreeMap<String, (String, Option<String>, u32, u32)>)> =
        BTreeMap::new();

    for audit in audits {
        let entry = rule_map.entry(audit.rule_index).or_insert_with(|| {
            (audit.rule_comment.clone(), 0, BTreeMap::new())
        });
        if audit.rule_fired {
            entry.1 += 1;
        }
        for cond in &audit.conditions {
            let centry = entry.2.entry(cond.path.clone()).or_insert_with(|| {
                (cond.kind.clone(), cond.comment.clone(), 0u32, 0u32)
            });
            if cond.result {
                centry.2 += 1;
            }
            if cond.insufficient_data {
                centry.3 += 1;
            }
        }
    }

    // Count bars per rule (number of BarAudit entries with this rule_index).
    let mut bars_per_rule: BTreeMap<u8, u32> = BTreeMap::new();
    for audit in audits {
        *bars_per_rule.entry(audit.rule_index).or_insert(0) += 1;
    }

    let rules = rule_map
        .into_iter()
        .map(|(rule_index, (rule_comment, times_fired, cond_map))| {
            let rule_bars = *bars_per_rule.get(&rule_index).unwrap_or(&1);
            let fire_rate = if rule_bars > 0 {
                times_fired as f64 / rule_bars as f64
            } else {
                0.0
            };

            let conditions = cond_map
                .into_iter()
                .map(|(path, (kind, comment, true_count, insufficient_data_count))| {
                    let true_rate = if rule_bars > 0 {
                        true_count as f64 / rule_bars as f64
                    } else {
                        0.0
                    };
                    ConditionPassRate {
                        path,
                        kind,
                        comment,
                        true_count,
                        true_rate,
                        insufficient_data_count,
                    }
                })
                .collect();

            RuleAuditSummary {
                rule_index,
                rule_comment,
                times_fired,
                fire_rate,
                conditions,
            }
        })
        .collect();

    ConditionAuditSummary { total_bars, rules }
}
