use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use uuid::Uuid;

/// RunDiff compares two reconciliation runs to detect identity changes.
/// Used for shadow mode / A-B testing of spec changes.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunDiff {
    pub baseline_run_id: String,
    pub candidate_run_id: String,
    pub entity: String,
    pub summary: DiffSummary,
    pub diffs: DiffDetails,
    pub risk_assessment: DiffRiskAssessment,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffSummary {
    pub total_records_compared: usize,
    pub identical_identities: usize,
    pub differences_detected: usize,
    /// Confidence shift metrics when comparing FS-scored runs
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub confidence_shift: Option<ConfidenceShift>,
}

/// Tracks how confidence scores shifted between two reconciliation runs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidenceShift {
    pub avg_log_likelihood_delta: f64,
    pub entities_below_match_threshold: usize,
    pub entities_above_possible_threshold: usize,
    pub non_deterministic_clusters: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffDetails {
    pub over_merges: Vec<OverMerge>,
    pub under_merges: Vec<UnderMerge>,
    pub survivorship_diffs: Vec<SurvivorshipDiff>,
}

/// Baseline had separate identities, candidate merged them.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OverMerge {
    pub canonical_id: Uuid,
    pub baseline_canonical_ids: Vec<Uuid>,
    pub entity_ids: Vec<Uuid>,
}

/// Baseline had merged identities, candidate kept them separate.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnderMerge {
    pub baseline_canonical_id: Uuid,
    pub candidate_canonical_ids: Vec<Uuid>,
    pub entity_ids: Vec<Uuid>,
}

/// Field-level difference in golden record survivorship.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurvivorshipDiff {
    pub canonical_id: Uuid,
    pub field: String,
    pub baseline_value: Option<String>,
    pub candidate_value: Option<String>,
    pub baseline_source: Option<String>,
    pub candidate_source: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffRiskAssessment {
    pub over_merge_count: usize,
    pub under_merge_count: usize,
    pub survivorship_change_count: usize,
    pub risk_level: String,
}

// ============================================================================
// Cluster Stability Metrics (A13)
// ============================================================================

/// Metrics comparing cluster assignments across sequential runs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterStabilityMetrics {
    /// Percentage of entities that changed cluster between runs
    pub churn_rate: f64,
    /// Rate of new merges relative to total pairs
    pub merge_volatility_index: f64,
    /// Rate of new splits relative to total clusters
    pub split_volatility_index: f64,
    /// Confidence decay over sequential runs
    pub confidence_decay: Vec<ConfidenceDecayPoint>,
    /// Percentage of clusters unchanged across runs
    pub stable_cluster_pct: f64,
}

/// A point in the confidence decay series across sequential runs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfidenceDecayPoint {
    pub run_id: String,
    pub timestamp: String,
    pub avg_confidence: f64,
    pub median_confidence: f64,
    pub p10_confidence: f64,
    pub entities_below_match: usize,
}

/// Cluster assignment: maps entity_id → cluster_id for comparison across runs.
pub type ClusterAssignment = BTreeMap<Uuid, Uuid>;

/// Build cluster assignments from a list of clusters.
/// Uses the smallest member UUID as the cluster_id for stability.
pub fn build_cluster_assignment(clusters: &[Vec<Uuid>]) -> ClusterAssignment {
    let mut assignment = BTreeMap::new();
    for cluster in clusters {
        if cluster.is_empty() {
            continue;
        }
        let cluster_id = *cluster.iter().min().unwrap();
        for &entity_id in cluster {
            assignment.insert(entity_id, cluster_id);
        }
    }
    assignment
}

impl ClusterStabilityMetrics {
    /// Compute stability metrics by comparing baseline and candidate cluster assignments.
    pub fn compute(
        baseline: &ClusterAssignment,
        candidate: &ClusterAssignment,
    ) -> Self {
        if baseline.is_empty() && candidate.is_empty() {
            return Self {
                churn_rate: 0.0,
                merge_volatility_index: 0.0,
                split_volatility_index: 0.0,
                confidence_decay: vec![],
                stable_cluster_pct: 100.0,
            };
        }

        // All entity IDs across both runs
        let all_entities: BTreeSet<Uuid> = baseline.keys().chain(candidate.keys()).copied().collect();
        let total = all_entities.len() as f64;

        // Churn: entities that changed cluster
        let mut changed = 0usize;
        for &entity in &all_entities {
            let b = baseline.get(&entity);
            let c = candidate.get(&entity);
            if b != c {
                changed += 1;
            }
        }
        let churn_rate = if total > 0.0 { changed as f64 / total } else { 0.0 };

        // Build cluster membership sets
        let baseline_clusters = Self::invert_assignment(baseline);
        let candidate_clusters = Self::invert_assignment(candidate);

        // Merge volatility: count pairs that are merged in candidate but not in baseline
        let baseline_pairs = Self::count_pairs(&baseline_clusters);
        let candidate_pairs = Self::count_pairs(&candidate_clusters);

        let new_merges = candidate_pairs.difference(&baseline_pairs).count();
        let total_candidate_pairs = candidate_pairs.len().max(1);
        let merge_volatility_index = new_merges as f64 / total_candidate_pairs as f64;

        // Split volatility: baseline clusters that got split
        let mut baseline_split_count = 0usize;
        for (_cluster_id, members) in &baseline_clusters {
            if members.len() < 2 {
                continue;
            }
            // Check if all members are still in the same candidate cluster
            let candidate_cluster_ids: BTreeSet<Option<&Uuid>> = members.iter()
                .map(|m| candidate.get(m))
                .collect();
            if candidate_cluster_ids.len() > 1 {
                baseline_split_count += 1;
            }
        }
        let total_baseline_clusters = baseline_clusters.len().max(1);
        let split_volatility_index = baseline_split_count as f64 / total_baseline_clusters as f64;

        // Stable clusters: baseline clusters entirely preserved in candidate
        let mut stable = 0usize;
        for (_cluster_id, members) in &baseline_clusters {
            let candidate_cluster_ids: BTreeSet<Option<&Uuid>> = members.iter()
                .map(|m| candidate.get(m))
                .collect();
            if candidate_cluster_ids.len() == 1 && candidate_cluster_ids.iter().next().unwrap().is_some() {
                // All members map to the same candidate cluster
                // Check that the candidate cluster has exactly these members
                let cid = candidate_cluster_ids.into_iter().next().unwrap().unwrap();
                if let Some(candidate_members) = candidate_clusters.get(cid) {
                    if candidate_members == members {
                        stable += 1;
                    }
                }
            }
        }
        let stable_cluster_pct = if total_baseline_clusters > 0 {
            (stable as f64 / total_baseline_clusters as f64) * 100.0
        } else {
            100.0
        };

        Self {
            churn_rate,
            merge_volatility_index,
            split_volatility_index,
            confidence_decay: vec![], // populated separately from historical data
            stable_cluster_pct,
        }
    }

    /// Invert assignment: cluster_id → set of entity_ids
    fn invert_assignment(assignment: &ClusterAssignment) -> BTreeMap<Uuid, BTreeSet<Uuid>> {
        let mut clusters: BTreeMap<Uuid, BTreeSet<Uuid>> = BTreeMap::new();
        for (&entity, &cluster) in assignment {
            clusters.entry(cluster).or_default().insert(entity);
        }
        clusters
    }

    /// Compute all within-cluster pairs as a set for diff comparison.
    fn count_pairs(clusters: &BTreeMap<Uuid, BTreeSet<Uuid>>) -> BTreeSet<(Uuid, Uuid)> {
        let mut pairs = BTreeSet::new();
        for members in clusters.values() {
            let sorted: Vec<Uuid> = members.iter().copied().collect();
            for i in 0..sorted.len() {
                for j in i + 1..sorted.len() {
                    pairs.insert((sorted[i], sorted[j]));
                }
            }
        }
        pairs
    }
}

// ============================================================================
// Run Diff Computation
// ============================================================================

impl RunDiff {
    /// Compute a run diff by comparing two sets of cluster assignments.
    pub fn compute(
        baseline_run_id: &str,
        candidate_run_id: &str,
        entity: &str,
        baseline_assignments: &ClusterAssignment,
        candidate_assignments: &ClusterAssignment,
        baseline_confidences: &HashMap<Uuid, f64>,
        candidate_confidences: &HashMap<Uuid, f64>,
        match_threshold: f64,
        possible_threshold: f64,
    ) -> Self {
        let all_entities: BTreeSet<Uuid> = baseline_assignments.keys()
            .chain(candidate_assignments.keys())
            .copied()
            .collect();
        let total = all_entities.len();

        // Invert assignments
        let baseline_clusters = ClusterStabilityMetrics::invert_assignment(baseline_assignments);
        let candidate_clusters = ClusterStabilityMetrics::invert_assignment(candidate_assignments);

        // Detect over-merges: baseline had separate clusters, candidate merged them
        let mut over_merges = Vec::new();
        for (cand_cluster_id, cand_members) in &candidate_clusters {
            if cand_members.len() < 2 {
                continue;
            }
            // Find baseline clusters these members came from
            let baseline_cluster_ids: BTreeSet<Uuid> = cand_members.iter()
                .filter_map(|m| baseline_assignments.get(m).copied())
                .collect();
            if baseline_cluster_ids.len() > 1 {
                over_merges.push(OverMerge {
                    canonical_id: *cand_cluster_id,
                    baseline_canonical_ids: baseline_cluster_ids.into_iter().collect(),
                    entity_ids: cand_members.iter().copied().collect(),
                });
            }
        }

        // Detect under-merges: baseline had merged cluster, candidate split it
        let mut under_merges = Vec::new();
        for (base_cluster_id, base_members) in &baseline_clusters {
            if base_members.len() < 2 {
                continue;
            }
            let candidate_cluster_ids: BTreeSet<Uuid> = base_members.iter()
                .filter_map(|m| candidate_assignments.get(m).copied())
                .collect();
            if candidate_cluster_ids.len() > 1 {
                under_merges.push(UnderMerge {
                    baseline_canonical_id: *base_cluster_id,
                    candidate_canonical_ids: candidate_cluster_ids.into_iter().collect(),
                    entity_ids: base_members.iter().copied().collect(),
                });
            }
        }

        // Confidence shift
        let confidence_shift = if !baseline_confidences.is_empty() && !candidate_confidences.is_empty() {
            let mut deltas = Vec::new();
            for entity in &all_entities {
                if let (Some(&bc), Some(&cc)) = (baseline_confidences.get(entity), candidate_confidences.get(entity)) {
                    deltas.push(cc - bc);
                }
            }
            let avg_delta = if deltas.is_empty() { 0.0 } else { deltas.iter().sum::<f64>() / deltas.len() as f64 };

            let entities_below_match = candidate_confidences.values()
                .filter(|&&c| c < match_threshold)
                .count();
            let entities_above_possible = candidate_confidences.values()
                .filter(|&&c| c >= possible_threshold)
                .count();

            // Non-deterministic: entities that changed cluster
            let non_det = all_entities.iter()
                .filter(|e| baseline_assignments.get(e) != candidate_assignments.get(e))
                .count();

            Some(ConfidenceShift {
                avg_log_likelihood_delta: avg_delta,
                entities_below_match_threshold: entities_below_match,
                entities_above_possible_threshold: entities_above_possible,
                non_deterministic_clusters: non_det,
            })
        } else {
            None
        };

        // Count identical
        let identical = all_entities.iter()
            .filter(|e| baseline_assignments.get(e) == candidate_assignments.get(e))
            .count();
        let differences = total - identical;

        let risk_level = if over_merges.len() + under_merges.len() > 50 {
            "high".to_string()
        } else if over_merges.len() + under_merges.len() > 10 {
            "medium".to_string()
        } else {
            "low".to_string()
        };

        Self {
            baseline_run_id: baseline_run_id.to_string(),
            candidate_run_id: candidate_run_id.to_string(),
            entity: entity.to_string(),
            summary: DiffSummary {
                total_records_compared: total,
                identical_identities: identical,
                differences_detected: differences,
                confidence_shift,
            },
            diffs: DiffDetails {
                over_merges,
                under_merges,
                survivorship_diffs: vec![], // survivorship diffs require golden record data
            },
            risk_assessment: DiffRiskAssessment {
                over_merge_count: 0, // set below
                under_merge_count: 0,
                survivorship_change_count: 0,
                risk_level,
            },
        }
    }
}

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

    fn uuid(n: u128) -> Uuid {
        Uuid::from_u128(n)
    }

    #[test]
    fn stability_identical_runs() {
        let mut baseline = BTreeMap::new();
        baseline.insert(uuid(1), uuid(1));
        baseline.insert(uuid(2), uuid(1));
        baseline.insert(uuid(3), uuid(3));

        let metrics = ClusterStabilityMetrics::compute(&baseline, &baseline);
        assert_eq!(metrics.churn_rate, 0.0);
        assert_eq!(metrics.merge_volatility_index, 0.0);
        assert_eq!(metrics.split_volatility_index, 0.0);
        assert_eq!(metrics.stable_cluster_pct, 100.0);
    }

    #[test]
    fn stability_detects_churn() {
        let mut baseline = BTreeMap::new();
        baseline.insert(uuid(1), uuid(1));
        baseline.insert(uuid(2), uuid(1));
        baseline.insert(uuid(3), uuid(3));

        // Candidate: entity 2 moved to cluster 3
        let mut candidate = BTreeMap::new();
        candidate.insert(uuid(1), uuid(1));
        candidate.insert(uuid(2), uuid(3));
        candidate.insert(uuid(3), uuid(3));

        let metrics = ClusterStabilityMetrics::compute(&baseline, &candidate);
        assert!(metrics.churn_rate > 0.0, "Should detect churn: {}", metrics.churn_rate);
        assert!(metrics.split_volatility_index > 0.0, "Should detect split");
    }

    #[test]
    fn stability_detects_merge() {
        // Baseline: two separate clusters
        let mut baseline = BTreeMap::new();
        baseline.insert(uuid(1), uuid(1));
        baseline.insert(uuid(2), uuid(2));

        // Candidate: merged into one
        let mut candidate = BTreeMap::new();
        candidate.insert(uuid(1), uuid(1));
        candidate.insert(uuid(2), uuid(1));

        let metrics = ClusterStabilityMetrics::compute(&baseline, &candidate);
        assert!(metrics.merge_volatility_index > 0.0, "Should detect new merge");
    }

    #[test]
    fn run_diff_detects_over_merge() {
        // Baseline: entities 1,2 separate
        let mut baseline = BTreeMap::new();
        baseline.insert(uuid(1), uuid(1));
        baseline.insert(uuid(2), uuid(2));
        baseline.insert(uuid(3), uuid(3));

        // Candidate: entities 1,2 merged
        let mut candidate = BTreeMap::new();
        candidate.insert(uuid(1), uuid(1));
        candidate.insert(uuid(2), uuid(1));
        candidate.insert(uuid(3), uuid(3));

        let diff = RunDiff::compute(
            "run-1", "run-2", "customer",
            &baseline, &candidate,
            &HashMap::new(), &HashMap::new(),
            0.8, 0.5,
        );

        assert_eq!(diff.summary.differences_detected, 1); // entity 2 moved
        assert_eq!(diff.diffs.over_merges.len(), 1);
        assert!(diff.diffs.under_merges.is_empty());
    }

    #[test]
    fn run_diff_detects_under_merge() {
        // Baseline: entities 1,2 merged
        let mut baseline = BTreeMap::new();
        baseline.insert(uuid(1), uuid(1));
        baseline.insert(uuid(2), uuid(1));

        // Candidate: entities 1,2 separate
        let mut candidate = BTreeMap::new();
        candidate.insert(uuid(1), uuid(1));
        candidate.insert(uuid(2), uuid(2));

        let diff = RunDiff::compute(
            "run-1", "run-2", "customer",
            &baseline, &candidate,
            &HashMap::new(), &HashMap::new(),
            0.8, 0.5,
        );

        assert_eq!(diff.diffs.under_merges.len(), 1);
        assert!(diff.diffs.over_merges.is_empty());
    }

    #[test]
    fn run_diff_confidence_shift() {
        let mut baseline = BTreeMap::new();
        baseline.insert(uuid(1), uuid(1));
        baseline.insert(uuid(2), uuid(1));

        let candidate = baseline.clone();

        let mut base_conf = HashMap::new();
        base_conf.insert(uuid(1), 6.0);
        base_conf.insert(uuid(2), 5.5);

        let mut cand_conf = HashMap::new();
        cand_conf.insert(uuid(1), 5.0);
        cand_conf.insert(uuid(2), 4.5);

        let diff = RunDiff::compute(
            "run-1", "run-2", "customer",
            &baseline, &candidate,
            &base_conf, &cand_conf,
            6.0, 2.0,
        );

        let shift = diff.summary.confidence_shift.unwrap();
        assert!(shift.avg_log_likelihood_delta < 0.0, "Confidence should decrease");
    }
}
