use pyo3::prelude::*;
use pyo3::types::PyDict;

// ── Helper: serde_json::Value → Python object ──────────────────────

fn json_value_to_py(py: Python<'_>, value: &serde_json::Value) -> PyResult<PyObject> {
    match value {
        serde_json::Value::Null => Ok(py.None()),
        serde_json::Value::Bool(b) => Ok(b.to_object(py)),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                Ok(i.to_object(py))
            } else if let Some(f) = n.as_f64() {
                Ok(f.to_object(py))
            } else {
                Ok(py.None())
            }
        }
        serde_json::Value::String(s) => Ok(s.to_object(py)),
        serde_json::Value::Array(arr) => {
            let list: Vec<PyObject> = arr
                .iter()
                .map(|v| json_value_to_py(py, v))
                .collect::<PyResult<_>>()?;
            Ok(list.to_object(py))
        }
        serde_json::Value::Object(map) => {
            let dict = PyDict::new_bound(py);
            for (k, v) in map {
                dict.set_item(k, json_value_to_py(py, v)?)?;
            }
            Ok(dict.into())
        }
    }
}

// ── Serde error translation ─────────────────────────────────────────

fn translate_serde_error(e: &serde_yaml::Error) -> Vec<String> {
    let msg = e.to_string();
    let location = e.location().map(|l| format!(" (line {})", l.line()));
    let loc = location.as_deref().unwrap_or("");

    // unknown variant 'X', expected one of ...
    if let Some(rest) = msg.strip_prefix("unknown variant `") {
        if let Some(tick_end) = rest.find('`') {
            let variant = &rest[..tick_end];
            let after = &rest[tick_end..];

            // Check for survivorship strategy context
            let survivorship_strategies = ["most_recent", "most_complete", "source_priority", "aggregate", "custom"];
            if survivorship_strategies.iter().any(|s| after.contains(s)) {
                return vec![format!(
                    "KNV-E201: Invalid survivorship strategy '{variant}'{loc}. Valid: {}",
                    survivorship_strategies.join(", ")
                )];
            }

            // Extract the expected list
            if let Some(exp_start) = after.find("expected one of ") {
                let expected = &after[exp_start + "expected one of ".len()..];
                let expected = expected.trim_end_matches(|c: char| c == '.' || c.is_whitespace());
                return vec![format!("KNV-E101: Invalid value '{variant}'{loc}. Valid options: {expected}")];
            }
        }
    }

    // missing field 'X'
    if msg.contains("missing field") {
        if let Some(start) = msg.find("missing field `") {
            let rest = &msg[start + "missing field `".len()..];
            if let Some(end) = rest.find('`') {
                let field = &rest[..end];
                // Check for survivorship override context
                if field == "field" && msg.contains("SurvivorshipOverride") {
                    return vec![format!(
                        "KNV-E203: Each survivorship override needs a 'field' name{loc}."
                    )];
                }
                return vec![format!(
                    "KNV-E102: Missing required field '{field}'{loc}. Add '{field}: <value>' to your spec."
                )];
            }
        }
    }

    // unknown field 'X', expected one of ...
    if let Some(rest) = msg.strip_prefix("unknown field `") {
        if let Some(tick_end) = rest.find('`') {
            let field = &rest[..tick_end];
            let after = &rest[tick_end..];

            // Try to find closest match for "did you mean" suggestion
            if let Some(exp_start) = after.find("expected one of ") {
                let expected_str = &after[exp_start + "expected one of ".len()..];
                let expected_str = expected_str.trim_end_matches(|c: char| c == '.' || c.is_whitespace());
                let candidates: Vec<&str> = expected_str.split(", ")
                    .map(|s| s.trim_matches('`').trim())
                    .collect();
                let suggestion = find_closest_match(field, &candidates);
                return match suggestion {
                    Some(s) => vec![format!("KNV-E103: Unknown field '{field}'{loc}. Did you mean '{s}'?")],
                    None => vec![format!("KNV-E103: Unknown field '{field}'{loc}. Expected one of: {expected_str}")],
                };
            }
        }
    }

    // invalid type: X, expected Y
    if msg.contains("invalid type:") {
        return vec![format!("KNV-E104: Type error{loc} — {msg}")];
    }

    // Fallback: pass through with a code prefix
    vec![format!("KNV-E100: {msg}")]
}

fn find_closest_match<'a>(target: &str, candidates: &[&'a str]) -> Option<&'a str> {
    let target_lower = target.to_lowercase();
    candidates.iter()
        .filter_map(|&c| {
            let c_lower = c.to_lowercase();
            let dist = edit_distance(&target_lower, &c_lower);
            if dist <= 2 { Some((c, dist)) } else { None }
        })
        .min_by_key(|(_, d)| *d)
        .map(|(c, _)| c)
}

fn edit_distance(a: &str, b: &str) -> usize {
    let a_chars: Vec<char> = a.chars().collect();
    let b_chars: Vec<char> = b.chars().collect();
    let m = a_chars.len();
    let n = b_chars.len();
    let mut dp = vec![vec![0usize; n + 1]; m + 1];
    for i in 0..=m { dp[i][0] = i; }
    for j in 0..=n { dp[0][j] = j; }
    for i in 1..=m {
        for j in 1..=n {
            let cost = if a_chars[i-1] == b_chars[j-1] { 0 } else { 1 };
            dp[i][j] = (dp[i-1][j] + 1).min(dp[i][j-1] + 1).min(dp[i-1][j-1] + cost);
        }
    }
    dp[m][n]
}

// ── PyO3 functions ─────────────────────────────────────────────────

#[pyfunction]
fn validate(yaml_str: &str) -> PyResult<Vec<String>> {
    kanoniv_core::validate_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
}

#[pyfunction]
fn validate_strict(yaml_str: &str) -> PyResult<Vec<String>> {
    // 1. Run existing permissive validator
    let mut errors = kanoniv_core::validate_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;

    // 2. Attempt strict serde deserialization — catches what the permissive validator misses
    match kanoniv_engine::spec::IdentitySpec::from_yaml(yaml_str) {
        Ok(_) => {},
        Err(e) => errors.extend(translate_serde_error(&e)),
    }

    Ok(errors)
}

#[pyfunction]
fn validate_schema(yaml_str: &str) -> PyResult<Vec<String>> {
    let spec = kanoniv_core::parse_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    kanoniv_core::validate_schema(&spec)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
}

#[pyfunction]
fn validate_semantics(yaml_str: &str) -> PyResult<Vec<String>> {
    let spec = kanoniv_core::parse_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    kanoniv_core::validate_semantics(&spec)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
}

#[pyfunction]
fn parse(py: Python<'_>, yaml_str: &str) -> PyResult<PyObject> {
    let value = kanoniv_core::parse_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    json_value_to_py(py, &value)
}

#[pyfunction]
fn compile_ir(py: Python<'_>, yaml_str: &str) -> PyResult<PyObject> {
    let spec = kanoniv_core::parse_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    let ir = kanoniv_core::compile_to_ir(&spec)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    json_value_to_py(py, &ir)
}

#[pyfunction]
fn diff(py: Python<'_>, yaml_a: &str, yaml_b: &str) -> PyResult<PyObject> {
    let result = kanoniv_core::compute_diff(yaml_a, yaml_b)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    let value = serde_json::to_value(&result)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    json_value_to_py(py, &value)
}

#[pyfunction]
fn hash(yaml_str: &str) -> PyResult<String> {
    use sha2::Digest;
    let spec: serde_json::Value = serde_yaml::from_str(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    let canonical = serde_json::to_string(&spec)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    let mut hasher = sha2::Sha256::new();
    hasher.update(canonical.as_bytes());
    Ok(format!("sha256:{:x}", hasher.finalize()))
}

#[pyfunction]
fn plan(py: Python<'_>, yaml_str: &str) -> PyResult<PyObject> {
    let result = kanoniv_core::generate_plan(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    let value = serde_json::to_value(&result)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))?;
    json_value_to_py(py, &value)
}

// ── Local reconciliation ──────────────────────────────────────────

#[pyfunction]
fn reconcile_local(py: Python<'_>, yaml_str: &str, entities_json: &str) -> PyResult<PyObject> {
    use kanoniv_engine::spec::IdentitySpec;
    use kanoniv_engine::compiler::SpecCompiler;
    use kanoniv_engine::ReconciliationEngine;
    use kanoniv_engine::types::NormalizedEntity;
    use kanoniv_engine::overrides::OverrideResolver;
    use kanoniv_engine::survivorship::SurvivorshipEngine;

    // 1. Parse spec YAML → IdentitySpec
    let spec = IdentitySpec::from_yaml(yaml_str)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid spec YAML: {e}")))?;

    // 2. Compile → IdentityPlan
    let plan = SpecCompiler::compile(&spec)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Compilation error: {e}")))?;

    // 3. Build engine
    let engine = ReconciliationEngine::from_plan(&plan);

    // 4. Deserialize entities
    let entities: Vec<NormalizedEntity> = serde_json::from_str(entities_json)
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid entities JSON: {e}")))?;

    // 5. Release GIL for rayon parallelism, run reconciliation with telemetry
    let resolver = OverrideResolver::new(vec![]);
    let (decisions, telemetry) = py.allow_threads(|| {
        engine.reconcile_with_telemetry(&entities, &resolver)
    });

    // 6. Cluster decisions
    let clusters = engine.cluster_decisions(&entities, &decisions);

    // 7. Golden records
    let golden_records: Vec<std::collections::HashMap<String, String>> = clusters.iter().map(|cluster_ids| {
        let cluster_entities: Vec<NormalizedEntity> = cluster_ids.iter()
            .filter_map(|id| entities.iter().find(|e| &e.id == id).cloned())
            .collect();
        SurvivorshipEngine::construct_golden_record(&cluster_entities, &plan.survivorship)
    }).collect();

    // 8. Serialize result to JSON → Python dict
    let result = serde_json::json!({
        "clusters": clusters,
        "golden_records": golden_records,
        "decisions": decisions,
        "telemetry": telemetry,
    });

    json_value_to_py(py, &result)
}

// ── Module definition ──────────────────────────────────────────────

#[pymodule]
fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(validate, m)?)?;
    m.add_function(wrap_pyfunction!(validate_strict, m)?)?;
    m.add_function(wrap_pyfunction!(validate_schema, m)?)?;
    m.add_function(wrap_pyfunction!(validate_semantics, m)?)?;
    m.add_function(wrap_pyfunction!(parse, m)?)?;
    m.add_function(wrap_pyfunction!(compile_ir, m)?)?;
    m.add_function(wrap_pyfunction!(diff, m)?)?;
    m.add_function(wrap_pyfunction!(hash, m)?)?;
    m.add_function(wrap_pyfunction!(plan, m)?)?;
    m.add_function(wrap_pyfunction!(reconcile_local, m)?)?;
    Ok(())
}
