use std::path::Path;

use serde_json::Value;
use tracing::{info, warn};

use crate::cache::NclCache;

/// Counts of records upserted per table during a seed pass.
pub struct SeedReport {
    pub nodes: usize,
    pub edges: usize,
    pub dimensions: usize,
    pub membranes: usize,
}

/// Seed ontology tables from local NCL files into SurrealDB.
///
/// All record IDs are prefixed with `slug` (`{slug}--{id}`) to ensure
/// multi-project isolation within shared tables. Records from different
/// projects never collide even when they share the same ontology ID.
#[cfg(feature = "db")]
pub async fn seed_ontology(
    db: &stratum_db::StratumDb,
    slug: &str,
    project_root: &Path,
    cache: &NclCache,
    import_path: Option<&str>,
) -> SeedReport {
    let mut report = SeedReport {
        nodes: 0,
        edges: 0,
        dimensions: 0,
        membranes: 0,
    };

    let core_path = project_root.join(".ontology").join("core.ncl");
    if core_path.exists() {
        match cache.export(&core_path, import_path).await {
            Ok((json, _)) => {
                report.nodes = seed_table_by_id(db, slug, "node", &json, "nodes").await;
                report.edges = seed_edges(db, slug, &json).await;
            }
            Err(e) => warn!(error = %e, "seed: core.ncl export failed"),
        }
    }

    let state_path = project_root.join(".ontology").join("state.ncl");
    if state_path.exists() {
        match cache.export(&state_path, import_path).await {
            Ok((json, _)) => {
                report.dimensions =
                    seed_table_by_id(db, slug, "dimension", &json, "dimensions").await;
            }
            Err(e) => warn!(error = %e, "seed: state.ncl export failed"),
        }
    }

    let gate_path = project_root.join(".ontology").join("gate.ncl");
    if gate_path.exists() {
        match cache.export(&gate_path, import_path).await {
            Ok((json, _)) => {
                report.membranes = seed_table_by_id(db, slug, "membrane", &json, "membranes").await;
            }
            Err(e) => warn!(error = %e, "seed: gate.ncl export failed"),
        }
    }

    info!(
        slug,
        nodes = report.nodes,
        edges = report.edges,
        dimensions = report.dimensions,
        membranes = report.membranes,
        "ontology seeded from local files"
    );

    report
}

/// Seed ontology tables from a pre-exported JSON payload (push-based sync).
///
/// `slug` scopes all record IDs — see `seed_ontology` for the isolation model.
#[cfg(feature = "db")]
pub async fn seed_from_payload(
    db: &stratum_db::StratumDb,
    slug: &str,
    payload: &serde_json::Value,
) -> SeedReport {
    let mut report = SeedReport {
        nodes: 0,
        edges: 0,
        dimensions: 0,
        membranes: 0,
    };

    if let Some(core) = payload.get("core") {
        report.nodes = seed_table_by_id(db, slug, "node", core, "nodes").await;
        report.edges = seed_edges(db, slug, core).await;
    }
    if let Some(state) = payload.get("state") {
        report.dimensions = seed_table_by_id(db, slug, "dimension", state, "dimensions").await;
    }
    if let Some(gate) = payload.get("gate") {
        report.membranes = seed_table_by_id(db, slug, "membrane", gate, "membranes").await;
    }

    info!(
        slug,
        nodes = report.nodes,
        edges = report.edges,
        dimensions = report.dimensions,
        membranes = report.membranes,
        "ontology seeded from push payload"
    );

    report
}

/// Generic upsert: extract `json[array_key]`, iterate, use each item's `id`
/// field as record key prefixed with `{slug}--` for multi-project isolation.
#[cfg(feature = "db")]
async fn seed_table_by_id(
    db: &stratum_db::StratumDb,
    slug: &str,
    table: &str,
    json: &Value,
    array_key: &str,
) -> usize {
    let items = match json.get(array_key).and_then(|a| a.as_array()) {
        Some(arr) => arr,
        None => return 0,
    };

    let mut count = 0;
    for item in items {
        let raw_id = match item.get("id").and_then(|i| i.as_str()) {
            Some(id) => id,
            None => continue,
        };
        let id = format!("{slug}--{raw_id}");
        // Rewrite the `id` field to the prefixed value so the content is
        // consistent with the record specifier (`table:id`). SurrealDB rejects
        // upserts where the `id` field in the content differs from the
        // targeted record ID.
        let mut record = item.clone();
        record["id"] = Value::String(id.clone());
        if let Err(e) = db.upsert(table, &id, record).await {
            warn!(table, id, error = %e, "seed: upsert failed");
            continue;
        }
        count += 1;
    }
    count
}

/// Edges use a deterministic compound key: `{slug}--{from}--{kind}--{to}`.
#[cfg(feature = "db")]
async fn seed_edges(db: &stratum_db::StratumDb, slug: &str, core_json: &Value) -> usize {
    let edges = match core_json.get("edges").and_then(|e| e.as_array()) {
        Some(arr) => arr,
        None => return 0,
    };

    let mut count = 0;
    for edge in edges {
        let from = edge.get("from").and_then(|f| f.as_str()).unwrap_or("");
        let to = edge.get("to").and_then(|t| t.as_str()).unwrap_or("");
        let kind = edge
            .get("kind")
            .and_then(|k| k.as_str())
            .unwrap_or("unknown");
        let edge_id = format!("{slug}--{from}--{kind}--{to}");

        if let Err(e) = db.upsert("edge", &edge_id, edge.clone()).await {
            warn!(edge_id, error = %e, "seed: edge upsert failed");
            continue;
        }
        count += 1;
    }
    count
}
