use serde::{Deserialize, Serialize};
use crate::types::NormalizedEntity;
use crate::spec::{
    IdentitySpec, ReferenceIdentifierSpec, RelationSpec, RuleSpec,
};

use crate::ir::{
    BlockingKey, BlockingTransformation, CompiledBlocking, CompiledReferenceIdentifier,
    CompiledRelation, CompiledRule, CompiledRuleType, DecisionConfig, FieldSurvivorshipPolicy,
    IdentityPlan, MatchGraph, ResolvedSource, SurvivorshipPolicy, TemporalConfig,
    CompiledFieldSchema, CompiledSampling,
};

use crate::observability::{
    PlanSummary, SourcesSummary, SourceDetail, MatchingSummary, RulesSummary, RuleDetail,
    BlockingSummary, SurvivorshipFieldSummary, ExecutionGraph, ScaleEstimate, RiskAnalysis,
    RiskFlag,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImpactAnalysis {
    pub total_entities: usize,
    pub distinct_blocks: usize,
    pub max_block_size: usize,
    pub potential_matches: usize,
    pub coverage_percent: f64,
}

#[derive(Debug, Clone)]
pub struct CompilationError {
    pub message: String,
}

impl std::fmt::Display for CompilationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for CompilationError {}

pub struct SpecCompiler;

impl SpecCompiler {
    pub fn compile(spec: &IdentitySpec) -> Result<IdentityPlan, CompilationError> {
        let sources = spec.sources
            .iter()
            .map(|s| {
                let adapter = s.adapter.as_ref()
                    .map(|a| format!("{:?}", a).to_lowercase())
                    .or_else(|| s.system.clone())
                    .unwrap_or_else(|| "unknown".to_string());
                let location = s.location.clone()
                    .or_else(|| s.table.clone())
                    .unwrap_or_else(|| "unknown".to_string());
                let id_field = s.primary_key.clone()
                    .or_else(|| s.id.clone())
                    .unwrap_or_else(|| "id".to_string());
                let schema = s.schema.as_ref().map(|sch| {
                    sch.iter().map(|(k, v)| (k.clone(), CompiledFieldSchema {
                        field_type: v.field_type.clone(),
                        pii: v.pii,
                        nullable: v.nullable,
                    })).collect()
                });
                let sampling = s.sampling.as_ref().map(|samp| CompiledSampling {
                    strategy: format!("{:?}", samp.strategy).to_lowercase(),
                    rate: samp.rate,
                });
                let pii_fields = s.schema.as_ref().map(|sch| {
                    sch.iter().filter(|(_, v)| v.pii).map(|(k, _)| k.clone()).collect()
                }).unwrap_or_default();

                ResolvedSource {
                    name: s.name.clone(),
                    adapter,
                    location,
                    id_field,
                    field_mappings: s.attributes.clone(),
                    temporal: s.temporal.as_ref().map(|t| TemporalConfig {
                        valid_from_field: t.valid_from.clone(),
                        valid_to_field: t.valid_to.clone(),
                        strategy: t.strategy.clone(),
                    }),
                    freshness: None,
                    schema,
                    sampling,
                    tags: s.tags.clone(),
                    pii_fields,
                }
            })
            .collect();

        let blocking = match &spec.blocking {
            Some(b) => {
                let keys = b.keys
                    .iter()
                    .map(|key_fields: &Vec<String>| BlockingKey {
                        fields: key_fields.clone(),
                        transformations: Self::infer_transformations(key_fields),
                    })
                    .collect();

                CompiledBlocking {
                    strategy: b.strategy.clone(),
                    keys,
                    fallback_sample_rate: b.fallback.as_ref().and_then(|f| f.sample_rate),
                    lsh_bands: None,
                    lsh_rows: None,
                    neighborhood_window: Some(50),
                    sort_fields: vec![],
                }
            }
            None => CompiledBlocking {
                strategy: crate::ir::BlockingStrategy::Composite,
                keys: vec![],
                fallback_sample_rate: None,
                lsh_bands: None,
                lsh_rows: None,
                neighborhood_window: None,
                sort_fields: vec![],
            },
        };

        let match_rules: Vec<CompiledRule> = spec.rules
            .iter()
            .map(Self::compile_rule)
            .collect();

        let leaf_count = match_rules.iter().map(Self::count_leaves).sum();

        let survivorship = match &spec.survivorship {
            Some(s) => {
                let mut field_policies = std::collections::HashMap::new();
                for override_spec in &s.overrides {
                    field_policies.insert(
                        override_spec.field.clone(),
                        FieldSurvivorshipPolicy {
                            strategy: override_spec.strategy.clone(),
                            source_priority: if override_spec.priority.is_empty() {
                                None
                            } else {
                                Some(override_spec.priority.clone())
                            },
                            aggregate_fn: override_spec.function.as_ref().map(|f| format!("{:?}", f)),
                        },
                    );
                }

                SurvivorshipPolicy {
                    default_strategy: s.default.clone(),
                    field_policies,
                }
            }
            None => SurvivorshipPolicy {
                default_strategy: crate::ir::SurvivorshipStrategy::MostRecent,
                field_policies: std::collections::HashMap::new(),
            },
        };

        let decision = DecisionConfig {
            match_threshold: spec.decision.thresholds.match_threshold,
            review_threshold: spec.decision.thresholds.review,
            reject_threshold: spec.decision.thresholds.reject,
            conflict_strategy: spec.decision.conflict_strategy.clone(),
            review_webhook: spec.decision.review_queue.as_ref().and_then(|r| r.webhook.clone()),
        };

        let reference_identifiers = spec.reference_identifiers
            .as_ref()
            .map(|ids| {
                ids.iter()
                    .map(|rid: &ReferenceIdentifierSpec| CompiledReferenceIdentifier {
                        name: rid.name.clone(),
                        id_type: rid.id_type.clone(),
                        authority: rid.authority.clone(),
                        field: rid.field.clone(),
                        match_weight: rid.match_weight,
                    })
                    .collect()
            })
            .unwrap_or_default();

        let relations = spec.relations
            .as_ref()
            .map(|rels| {
                rels.iter()
                    .map(|rel: &RelationSpec| CompiledRelation {
                        name: rel.name.clone(),
                        from_entity: rel.from_entity.clone(),
                        to_entity: rel.to_entity.clone(),
                        join_key: rel.join_key.clone(),
                        cardinality: rel.cardinality.clone(),
                        propagate_match: rel.propagate_match,
                    })
                    .collect()
            })
            .unwrap_or_default();

        let mut plan = IdentityPlan {
            entity: spec.entity.name.clone(),
            plan_hash: String::new(),
            identity_version: spec.identity_version.clone(),
            sources,
            blocking,
            match_graph: MatchGraph { rules: match_rules, leaf_count },
            survivorship,
            decision,
            reference_identifiers,
            relations,
            governance: None,
        };

        plan.plan_hash = plan.compute_hash();
        Ok(plan)
    }

    fn infer_transformations(fields: &[String]) -> Vec<BlockingTransformation> {
        let mut transforms = Vec::new();
        for field in fields {
            let field_lower = field.to_lowercase();
            if field_lower.contains("initial") {
                transforms.push(BlockingTransformation::FirstInitial);
            } else if field_lower.contains("soundex") {
                transforms.push(BlockingTransformation::Soundex);
            } else if field_lower.contains("area_code") {
                transforms.push(BlockingTransformation::AreaCode);
            } else if field_lower.contains("last4") || field_lower.contains("last_4") {
                transforms.push(BlockingTransformation::Last4);
            } else if field_lower.contains("trigram") {
                transforms.push(BlockingTransformation::Trigrams);
            } else if field_lower == "phone"
                || field_lower.contains("phone")
                || field_lower.contains("mobile")
                || field_lower.contains("cell")
                || field_lower.contains("tel")
            {
                transforms.push(BlockingTransformation::NormalizePhone);
            } else if field_lower == "email" || field_lower.contains("email") {
                transforms.push(BlockingTransformation::NormalizeEmail);
            } else if field_lower == "name"
                || field_lower.contains("name")
            {
                transforms.push(BlockingTransformation::NormalizeName);
            } else if field_lower == "website"
                || field_lower == "domain"
                || field_lower == "homepage"
                || field_lower.contains("website")
                || field_lower.contains("domain")
                || field_lower.contains("url")
            {
                transforms.push(BlockingTransformation::NormalizeDomain);
            }
        }
        if transforms.is_empty() {
            transforms.push(BlockingTransformation::Lowercase);
        }
        transforms
    }

    fn compile_rule(rule: &RuleSpec) -> CompiledRule {
        let rule_type = match rule {
            RuleSpec::Exact {
                field,
                weight,
                options,
                ..
            } => {
                let opts = options.clone().unwrap_or_default();
                CompiledRuleType::Exact {
                    field: field.clone(),
                    weight: *weight,
                    case_insensitive: opts.case_insensitive,
                    normalize: opts.normalize,
                }
            }
            RuleSpec::Similarity {
                field,
                algorithm,
                threshold,
                weight,
                ..
            } => CompiledRuleType::Similarity {
                field: field.clone(),
                algorithm: algorithm.clone(),
                threshold: *threshold,
                weight: *weight,
            },
            RuleSpec::Range {
                field,
                tolerance,
                weight,
                ..
            } => CompiledRuleType::Range {
                field: field.clone(),
                tolerance: *tolerance,
                weight: *weight,
            },
            RuleSpec::Composite {
                operator, children, ..
            } => {
                let compiled_children = children
                    .iter()
                    .map(Self::compile_rule)
                    .collect();
                CompiledRuleType::Composite {
                    operator: operator.clone(),
                    children: compiled_children,
                }
            }
        };

        CompiledRule {
            name: Self::get_rule_name(rule),
            rule_type,
        }
    }

    fn get_rule_name(rule: &RuleSpec) -> String {
        match rule {
            RuleSpec::Exact { name, .. } => name.clone(),
            RuleSpec::Similarity { name, .. } => name.clone(),
            RuleSpec::Range { name, .. } => name.clone(),
            RuleSpec::Composite { name, .. } => name.clone(),
        }
    }

    fn count_leaves(rule: &CompiledRule) -> usize {
        match &rule.rule_type {
            CompiledRuleType::Composite { children, .. } => {
                children.iter().map(Self::count_leaves).sum()
            }
            _ => 1,
        }
    }

    pub fn summarize(spec: &IdentitySpec, plan: &IdentityPlan) -> PlanSummary {
        // Sources
        let sources = SourcesSummary {
            count: spec.sources.len(),
            list: spec.sources.iter().map(|s| {
                let location = s.location.clone()
                    .or_else(|| s.table.clone())
                    .unwrap_or_else(|| "unknown".to_string());
                let key = s.primary_key.clone()
                    .or_else(|| s.id.clone())
                    .unwrap_or_else(|| "id".to_string());
                SourceDetail {
                    name: s.name.clone(),
                    table: location,
                    key,
                    fields: s.attributes.keys().cloned().collect(),
                }
            }).collect(),
        };

        // Matching rules
        let mut by_type: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
        let mut rules_detail = Vec::new();
        for rule in &plan.match_graph.rules {
            Self::collect_rule_details(rule, &mut by_type, &mut rules_detail);
        }
        let matching = MatchingSummary {
            rules: RulesSummary {
                total: rules_detail.len(),
                by_type,
            },
            rules_detail,
        };

        // Blocking
        let blocking_enabled = spec.blocking.is_some() && !plan.blocking.keys.is_empty();
        let blocking = BlockingSummary {
            enabled: blocking_enabled,
            strategy: format!("{:?}", plan.blocking.strategy).to_lowercase(),
            keys: plan.blocking.keys.iter().map(|k| k.fields.clone()).collect(),
            estimated_reduction_factor: if blocking_enabled { 0.9 } else { 0.0 },
        };

        // Survivorship
        let mut survivorship_fields = Vec::new();
        survivorship_fields.push(SurvivorshipFieldSummary {
            field: "*".to_string(),
            strategy: format!("{:?}", plan.survivorship.default_strategy).to_lowercase(),
            source_priority: None,
        });
        for (field, policy) in &plan.survivorship.field_policies {
            survivorship_fields.push(SurvivorshipFieldSummary {
                field: field.clone(),
                strategy: format!("{:?}", policy.strategy).to_lowercase(),
                source_priority: policy.source_priority.clone(),
            });
        }

        // Execution graph
        let mut stages = vec!["ingest".to_string(), "block".to_string(), "match".to_string(), "score".to_string(), "resolve".to_string()];
        if !plan.survivorship.field_policies.is_empty() || plan.survivorship.default_strategy != crate::ir::SurvivorshipStrategy::MostRecent {
            stages.push("survivorship".to_string());
        }
        let execution_graph = ExecutionGraph {
            dag_depth: stages.len(),
            parallelizable_stages: vec!["block".to_string(), "match".to_string()],
            stages,
        };

        // Scale estimate
        let scale_estimate = if blocking_enabled {
            ScaleEstimate {
                estimated_comparisons: "depends on blocking distribution".to_string(),
                complexity_class: "O(n log n)".to_string(),
            }
        } else {
            ScaleEstimate {
                estimated_comparisons: "O(n²) — no blocking".to_string(),
                complexity_class: "O(n²)".to_string(),
            }
        };

        // Risk analysis
        let mut flags = Vec::new();
        let mut risk_score: f64 = 0.0;

        if !blocking_enabled {
            flags.push(RiskFlag {
                code: "no_blocking".to_string(),
                severity: "high".to_string(),
                description: "No blocking strategy configured — all-pairs comparison is O(n²)".to_string(),
            });
            risk_score += 0.4;
        }

        let has_fuzzy = matching.rules_detail.iter().any(|r| r.rule_type == "similarity");
        if has_fuzzy && matching.rules_detail.iter().all(|r| r.rule_type == "similarity") {
            flags.push(RiskFlag {
                code: "all_fuzzy".to_string(),
                severity: "medium".to_string(),
                description: "All matching rules are fuzzy — results may be non-deterministic".to_string(),
            });
            risk_score += 0.2;
        }

        if spec.sources.len() == 1 {
            flags.push(RiskFlag {
                code: "single_source".to_string(),
                severity: "low".to_string(),
                description: "Only one data source — deduplication within a single system".to_string(),
            });
            risk_score += 0.05;
        }

        if plan.decision.match_threshold < 0.5 {
            flags.push(RiskFlag {
                code: "low_threshold".to_string(),
                severity: "high".to_string(),
                description: format!("Match threshold {:.2} is below 0.50 — high risk of over-merging", plan.decision.match_threshold),
            });
            risk_score += 0.3;
        }

        if plan.match_graph.rules.is_empty() {
            flags.push(RiskFlag {
                code: "no_rules".to_string(),
                severity: "critical".to_string(),
                description: "No matching rules defined — reconciliation will produce no matches".to_string(),
            });
            risk_score = 1.0;
        }

        let risk_analysis = RiskAnalysis {
            flags,
            overall_risk_score: risk_score.min(1.0),
        };

        PlanSummary {
            plan_hash: plan.plan_hash.clone(),
            api_version: spec.api_version.clone(),
            entity: plan.entity.clone(),
            spec_version: plan.identity_version.clone(),
            sources,
            matching,
            blocking,
            survivorship: survivorship_fields,
            execution_graph,
            scale_estimate,
            risk_analysis,
        }
    }

    fn collect_rule_details(
        rule: &CompiledRule,
        by_type: &mut std::collections::HashMap<String, usize>,
        details: &mut Vec<RuleDetail>,
    ) {
        match &rule.rule_type {
            CompiledRuleType::Exact { field, weight, .. } => {
                *by_type.entry("exact".to_string()).or_insert(0) += 1;
                details.push(RuleDetail {
                    name: rule.name.clone(),
                    rule_type: "exact".to_string(),
                    field: Some(field.clone()),
                    algorithm: None,
                    threshold: None,
                    weight: Some(*weight),
                });
            }
            CompiledRuleType::Similarity { field, algorithm, threshold, weight } => {
                *by_type.entry("similarity".to_string()).or_insert(0) += 1;
                details.push(RuleDetail {
                    name: rule.name.clone(),
                    rule_type: "similarity".to_string(),
                    field: Some(field.clone()),
                    algorithm: Some(format!("{:?}", algorithm).to_lowercase()),
                    threshold: Some(*threshold),
                    weight: Some(*weight),
                });
            }
            CompiledRuleType::Range { field, tolerance, weight } => {
                *by_type.entry("range".to_string()).or_insert(0) += 1;
                details.push(RuleDetail {
                    name: rule.name.clone(),
                    rule_type: "range".to_string(),
                    field: Some(field.clone()),
                    algorithm: None,
                    threshold: Some(*tolerance),
                    weight: Some(*weight),
                });
            }
            CompiledRuleType::Composite { children, .. } => {
                *by_type.entry("composite".to_string()).or_insert(0) += 1;
                details.push(RuleDetail {
                    name: rule.name.clone(),
                    rule_type: "composite".to_string(),
                    field: None,
                    algorithm: None,
                    threshold: None,
                    weight: None,
                });
                for child in children {
                    Self::collect_rule_details(child, by_type, details);
                }
            }
        }
    }

    pub fn estimate_impact(plan: &IdentityPlan, entities: &[NormalizedEntity]) -> ImpactAnalysis {
        use std::collections::HashMap;
        let mut block_counts = HashMap::new();
        let engine = crate::ReconciliationEngine::from_plan(plan);

        for entity in entities {
            let keys = engine.generate_block_keys(entity);
            for key in keys {
                *block_counts.entry(key).or_insert(0) += 1;
            }
        }

        let distinct_blocks = block_counts.len();
        let max_block_size = block_counts.values().cloned().max().unwrap_or(0);
        let mut potential_matches = 0;
        for count in block_counts.values() {
            if *count > 1 {
                potential_matches += (count * (count - 1)) / 2;
            }
        }

        let entities_with_blocks = entities.iter()
            .filter(|e| !engine.generate_block_keys(e).is_empty())
            .count();

        ImpactAnalysis {
            total_entities: entities.len(),
            distinct_blocks,
            max_block_size,
            potential_matches,
            coverage_percent: if entities.is_empty() { 0.0 } else { (entities_with_blocks as f64 / entities.len() as f64) * 100.0 },
        }
    }
}
