use serde::{Deserialize, Serialize};
use std::collections::HashMap;

// ============================================================================
// PLAN.SUMMARY — static analysis of a compiled identity plan
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanSummary {
    pub plan_hash: String,
    pub api_version: String,
    pub entity: String,
    pub spec_version: String,

    pub sources: SourcesSummary,
    pub matching: MatchingSummary,
    pub blocking: BlockingSummary,
    pub survivorship: Vec<SurvivorshipFieldSummary>,
    pub execution_graph: ExecutionGraph,
    pub scale_estimate: ScaleEstimate,
    pub risk_analysis: RiskAnalysis,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourcesSummary {
    pub count: usize,
    pub list: Vec<SourceDetail>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceDetail {
    pub name: String,
    pub table: String,
    pub key: String,
    pub fields: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchingSummary {
    pub rules: RulesSummary,
    pub rules_detail: Vec<RuleDetail>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RulesSummary {
    pub total: usize,
    pub by_type: HashMap<String, usize>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleDetail {
    pub name: String,
    #[serde(rename = "type")]
    pub rule_type: String,
    pub field: Option<String>,
    pub algorithm: Option<String>,
    pub threshold: Option<f64>,
    pub weight: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockingSummary {
    pub enabled: bool,
    pub strategy: String,
    pub keys: Vec<Vec<String>>,
    pub estimated_reduction_factor: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurvivorshipFieldSummary {
    pub field: String,
    pub strategy: String,
    pub source_priority: Option<Vec<String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionGraph {
    pub stages: Vec<String>,
    pub dag_depth: usize,
    pub parallelizable_stages: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScaleEstimate {
    pub estimated_comparisons: String,
    pub complexity_class: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskAnalysis {
    pub flags: Vec<RiskFlag>,
    pub overall_risk_score: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskFlag {
    pub code: String,
    pub severity: String,
    pub description: String,
}

// ============================================================================
// IDENTITY.SUMMARY — post-reconciliation metrics
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdentitySummary {
    pub run_id: String,
    pub plan_hash: String,
    pub entity: String,
    pub timestamp: String,

    pub input: InputSummary,
    pub output: OutputSummary,
    pub clustering: ClusteringSummary,
    pub match_quality: MatchQualitySummary,
    pub survivorship: SurvivorshipSummary,
    pub stability: StabilitySummary,
    pub health_flags: Vec<HealthFlag>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputSummary {
    pub total_source_records: usize,
    pub by_source: HashMap<String, usize>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputSummary {
    pub canonical_identities: usize,
    pub merge_rate: f64,
    pub singleton_rate: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusteringSummary {
    pub average_cluster_size: f64,
    pub max_cluster_size: usize,
    pub clusters_by_size: HashMap<String, usize>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchQualitySummary {
    pub accepted_matches: usize,
    pub rejected_matches: usize,
    pub ambiguous_matches: usize,
    pub average_match_score: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurvivorshipSummary {
    pub fields_resolved: usize,
    pub conflicts_detected: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StabilitySummary {
    pub deterministic_merges: usize,
    pub fuzzy_merges: usize,
    pub fuzzy_merge_ratio: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthFlag {
    pub code: String,
    pub severity: String,
    pub message: String,
}

// ============================================================================
// RULE TELEMETRY — per-rule metrics collected during reconciliation
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleTelemetry {
    pub rule_name: String,
    pub evaluated: usize,
    pub matched: usize,
    pub skipped: usize,
    pub skip_reason: Option<String>,
    pub avg_score: f64,
}

// ============================================================================
// RECONCILIATION TELEMETRY — aggregate metrics from a reconciliation run
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ReconciliationTelemetry {
    pub rule_telemetry: Vec<RuleTelemetry>,
    pub blocking_groups: usize,
    pub pairs_evaluated: usize,
    pub decisions_by_type: HashMap<String, usize>,
}

// ============================================================================
// RUN HEALTH — derived health status from identity summary + telemetry
// ============================================================================

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunHealth {
    pub status: HealthStatus,
    pub signals: Vec<HealthSignal>,
    pub recommendations: Vec<String>,
    pub rule_telemetry: Vec<RuleTelemetry>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HealthStatus {
    Healthy,
    Degraded,
    Unhealthy,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthSignal {
    pub id: String,
    pub severity: String,
    pub message: String,
    pub likely_causes: Vec<String>,
}

impl RunHealth {
    pub fn derive(summary: &IdentitySummary, telemetry: &ReconciliationTelemetry) -> Self {
        let mut signals = Vec::new();
        let mut recommendations = Vec::new();

        // zero_matches: canonical_identities == 0 && input > 0
        if summary.output.canonical_identities == 0 && summary.input.total_source_records > 0 {
            signals.push(HealthSignal {
                id: "zero_matches".to_string(),
                severity: "critical".to_string(),
                message: format!(
                    "No canonical identities created from {} source records",
                    summary.input.total_source_records
                ),
                likely_causes: vec![
                    "Blocking keys too restrictive — no entity pairs formed".to_string(),
                    "Match thresholds too high for the data".to_string(),
                    "Field mappings incorrect — matching fields are empty".to_string(),
                ],
            });
            recommendations.push("Review blocking key configuration and verify field mappings populate matching fields".to_string());
        }

        // zero_merges: merge_rate == 0 && input > 1
        if summary.output.merge_rate == 0.0 && summary.input.total_source_records > 1 {
            signals.push(HealthSignal {
                id: "zero_merges".to_string(),
                severity: "high".to_string(),
                message: "No records were merged — every record became its own canonical identity".to_string(),
                likely_causes: vec![
                    "No overlapping values across sources for matching fields".to_string(),
                    "Match threshold too high".to_string(),
                    "Blocking keys preventing candidate pair formation".to_string(),
                ],
            });
            recommendations.push("Lower match thresholds or add fuzzy matching rules".to_string());
        }

        // all_singletons: all clusters size 1
        if summary.clustering.max_cluster_size <= 1 && summary.input.total_source_records > 1 {
            signals.push(HealthSignal {
                id: "all_singletons".to_string(),
                severity: "high".to_string(),
                message: "All clusters contain only a single record".to_string(),
                likely_causes: vec![
                    "Blocking strategy prevents pair formation".to_string(),
                    "Match rules are too strict".to_string(),
                ],
            });
            recommendations.push("Consider broadening blocking keys or adding composite rules".to_string());
        }

        // large_cluster: max_cluster_size > 10
        if summary.clustering.max_cluster_size > 10 {
            signals.push(HealthSignal {
                id: "large_cluster".to_string(),
                severity: "medium".to_string(),
                message: format!(
                    "Largest cluster contains {} records — possible over-merging",
                    summary.clustering.max_cluster_size
                ),
                likely_causes: vec![
                    "Fuzzy threshold too low, causing transitive merges".to_string(),
                    "Common values in matching fields (e.g., generic names)".to_string(),
                ],
            });
            recommendations.push("Add reference identifiers or tighten fuzzy thresholds to reduce cluster size".to_string());
        }

        // rules_never_triggered: any rule with evaluated > 0 but matched == 0
        for rt in &telemetry.rule_telemetry {
            if rt.evaluated > 0 && rt.matched == 0 {
                signals.push(HealthSignal {
                    id: "rules_never_triggered".to_string(),
                    severity: "medium".to_string(),
                    message: format!(
                        "Rule '{}' was evaluated {} times but never matched",
                        rt.rule_name, rt.evaluated
                    ),
                    likely_causes: vec![
                        "Threshold too high for the data distribution".to_string(),
                        "Field values never overlap across sources".to_string(),
                    ],
                });
            }
        }

        // rules_never_evaluated: any rule with evaluated == 0
        for rt in &telemetry.rule_telemetry {
            if rt.evaluated == 0 {
                signals.push(HealthSignal {
                    id: "rules_never_evaluated".to_string(),
                    severity: "medium".to_string(),
                    message: format!("Rule '{}' was never evaluated", rt.rule_name),
                    likely_causes: vec![
                        "Blocking strategy excludes all pairs for this rule's field".to_string(),
                        "No entity pairs were formed".to_string(),
                    ],
                });
            }
        }

        // high_fuzzy_ratio: fuzzy_merge_ratio > 0.5
        if summary.stability.fuzzy_merge_ratio > 0.5 {
            signals.push(HealthSignal {
                id: "high_fuzzy_ratio".to_string(),
                severity: "low".to_string(),
                message: format!(
                    "{:.0}% of merges rely on fuzzy matching — results may vary with data changes",
                    summary.stability.fuzzy_merge_ratio * 100.0
                ),
                likely_causes: vec![
                    "Few exact-match fields available".to_string(),
                    "Data quality requires fuzzy matching".to_string(),
                ],
            });
            recommendations.push("Add exact matching rules (email, phone, SSN) where possible to improve stability".to_string());
        }

        // low_avg_confidence: average_match_score < 0.7
        if summary.match_quality.average_match_score < 0.7 && summary.match_quality.accepted_matches > 0 {
            signals.push(HealthSignal {
                id: "low_avg_confidence".to_string(),
                severity: "medium".to_string(),
                message: format!(
                    "Average match confidence is {:.2} — below 0.70 threshold",
                    summary.match_quality.average_match_score
                ),
                likely_causes: vec![
                    "Fuzzy thresholds are close to the merge threshold".to_string(),
                    "Low-quality or incomplete source data".to_string(),
                ],
            });
            recommendations.push("Consider increasing match thresholds or improving data quality upstream".to_string());
        }

        // Derive overall status
        let status = if signals.iter().any(|s| s.severity == "critical") {
            HealthStatus::Unhealthy
        } else if signals.iter().any(|s| s.severity == "high") {
            HealthStatus::Degraded
        } else {
            HealthStatus::Healthy
        };

        RunHealth {
            status,
            signals,
            recommendations,
            rule_telemetry: telemetry.rule_telemetry.clone(),
        }
    }
}

// ============================================================================
// Telemetry Collector — accumulates per-rule counters during reconciliation
// ============================================================================

#[derive(Debug, Default)]
pub struct TelemetryCollector {
    pub rule_stats: HashMap<String, RuleStats>,
    pub pairs_evaluated: usize,
    pub blocking_groups: usize,
    pub decisions: HashMap<String, usize>,
}

#[derive(Debug, Default, Clone)]
pub struct RuleStats {
    pub evaluated: usize,
    pub matched: usize,
    pub skipped: usize,
    pub total_score: f64,
}

impl TelemetryCollector {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn record_pair(&mut self, rule_results: &[crate::types::RuleResult], decision_type: &str) {
        self.pairs_evaluated += 1;
        *self.decisions.entry(decision_type.to_string()).or_insert(0) += 1;

        for rr in rule_results {
            let stats = self.rule_stats.entry(rr.rule_name.clone()).or_default();
            stats.evaluated += 1;
            stats.total_score += rr.score;
            if rr.matched {
                stats.matched += 1;
            }
        }
    }

    pub fn into_telemetry(self) -> ReconciliationTelemetry {
        let rule_telemetry = self.rule_stats.into_iter().map(|(name, stats)| {
            RuleTelemetry {
                rule_name: name,
                evaluated: stats.evaluated,
                matched: stats.matched,
                skipped: stats.skipped,
                skip_reason: None,
                avg_score: if stats.evaluated > 0 {
                    stats.total_score / stats.evaluated as f64
                } else {
                    0.0
                },
            }
        }).collect();

        ReconciliationTelemetry {
            rule_telemetry,
            blocking_groups: self.blocking_groups,
            pairs_evaluated: self.pairs_evaluated,
            decisions_by_type: self.decisions,
        }
    }
}
