use surrealdb::engine::any::Any;
use surrealdb::Surreal;
use tracing::info;

use crate::Error;

/// Create all namespaced tables if they don't exist.
/// Idempotent — safe to run on every startup.
pub async fn migrate(db: &Surreal<Any>) -> Result<(), Error> {
    // orchestrator namespace
    db.query("USE NS orchestrator DB provisioning")
        .await
        .map_err(|e| Error::Migration(e.to_string()))?;
    db.query(
        r#"
        DEFINE TABLE IF NOT EXISTS tasks SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS task_events SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS execution_logs SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS config_hashes SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS provider_cache SCHEMALESS;
    "#,
    )
    .await
    .map_err(|e| Error::Migration(e.to_string()))?;
    info!(namespace = "orchestrator", "tables ready");

    // vault namespace
    db.query("USE NS vault DB provisioning")
        .await
        .map_err(|e| Error::Migration(e.to_string()))?;
    db.query(
        r#"
        DEFINE TABLE IF NOT EXISTS secrets SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS keys SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS leases SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS secret_versions SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS audit_trail SCHEMALESS;
    "#,
    )
    .await
    .map_err(|e| Error::Migration(e.to_string()))?;
    info!(namespace = "vault", "tables ready");

    // control_center namespace
    db.query("USE NS control_center DB provisioning")
        .await
        .map_err(|e| Error::Migration(e.to_string()))?;
    db.query(
        r#"
        DEFINE TABLE IF NOT EXISTS users SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS sessions SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS cedar_policies SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS policy_evaluations SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS roles SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS permissions SCHEMALESS;
    "#,
    )
    .await
    .map_err(|e| Error::Migration(e.to_string()))?;
    info!(namespace = "control_center", "tables ready");

    // audit namespace
    db.query("USE NS audit DB provisioning")
        .await
        .map_err(|e| Error::Migration(e.to_string()))?;
    db.query(
        r#"
        DEFINE TABLE IF NOT EXISTS events SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS metrics SCHEMALESS;
    "#,
    )
    .await
    .map_err(|e| Error::Migration(e.to_string()))?;
    info!(namespace = "audit", "tables ready");

    // workspace namespace
    db.query("USE NS workspace DB provisioning")
        .await
        .map_err(|e| Error::Migration(e.to_string()))?;
    db.query(
        r#"
        DEFINE TABLE IF NOT EXISTS registrations SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS deployments SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS git_sync_state SCHEMALESS;
        DEFINE TABLE IF NOT EXISTS extensions SCHEMALESS;
    "#,
    )
    .await
    .map_err(|e| Error::Migration(e.to_string()))?;
    info!(namespace = "workspace", "tables ready");

    // infra namespace — resource actual state + DAG graph
    db.query("USE NS infra DB provisioning")
        .await
        .map_err(|e| Error::Migration(e.to_string()))?;
    db.query(
        r#"
        -- Actual state of any provider resource (server, fip, network, firewall, volume, snapshot).
        -- `desired` is option<object>: populated when a full NCL reconcile runs; absent when the
        -- bridge writes live state only (bootstrap trigger, drift detection).
        DEFINE TABLE IF NOT EXISTS resource SCHEMAFULL;
        DEFINE FIELD IF NOT EXISTS workspace   ON resource TYPE string;
        DEFINE FIELD IF NOT EXISTS kind        ON resource TYPE string;
        DEFINE FIELD IF NOT EXISTS name        ON resource TYPE string;
        DEFINE FIELD IF NOT EXISTS provider_id ON resource TYPE option<string>;
        DEFINE FIELD IF NOT EXISTS state       ON resource TYPE string;
        DEFINE FIELD IF NOT EXISTS desired     ON resource TYPE option<object>;
        DEFINE FIELD IF NOT EXISTS actual      ON resource TYPE option<object>;
        DEFINE FIELD IF NOT EXISTS created_at  ON resource TYPE datetime VALUE $before OR time::now();
        DEFINE FIELD IF NOT EXISTS updated_at  ON resource TYPE datetime VALUE time::now();
        DEFINE FIELD IF NOT EXISTS last_synced ON resource TYPE option<datetime>;
        DEFINE INDEX IF NOT EXISTS resource_ws_kind_name ON resource FIELDS workspace, kind, name UNIQUE;

        -- DAG structural definition (imported from dag.ncl at reconcile time)
        DEFINE TABLE IF NOT EXISTS dag SCHEMAFULL;
        DEFINE FIELD IF NOT EXISTS workspace  ON dag TYPE string;
        DEFINE FIELD IF NOT EXISTS name       ON dag TYPE string;
        DEFINE FIELD IF NOT EXISTS version    ON dag TYPE string;
        DEFINE FIELD IF NOT EXISTS created_at ON dag TYPE datetime VALUE $before OR time::now();
        DEFINE INDEX IF NOT EXISTS dag_ws_name ON dag FIELDS workspace, name UNIQUE;

        -- DAG node: one formula entry (populated from dag.ncl structural import)
        DEFINE TABLE IF NOT EXISTS dag_node SCHEMAFULL;
        DEFINE FIELD IF NOT EXISTS dag        ON dag_node TYPE record<dag>;
        DEFINE FIELD IF NOT EXISTS formula_id ON dag_node TYPE string;
        DEFINE FIELD IF NOT EXISTS kind       ON dag_node TYPE string;
        DEFINE FIELD IF NOT EXISTS label      ON dag_node TYPE string;
        DEFINE INDEX IF NOT EXISTS dag_node_key ON dag_node FIELDS dag, formula_id UNIQUE;

        -- Dependency edges between dag nodes (graph relation for native traversal)
        DEFINE TABLE IF NOT EXISTS node_depends_on SCHEMAFULL TYPE RELATION IN dag_node OUT dag_node;

        -- DAG run: one execution instance (triggered by rad patch, manual, reconcile, schedule).
        -- Written by the NATS bridge on formula events — no `dag` record reference at write time;
        -- the `workspace` field is the correlation key used until a full dag import is available.
        DEFINE TABLE IF NOT EXISTS dag_run SCHEMAFULL;
        DEFINE FIELD IF NOT EXISTS workspace   ON dag_run TYPE string;
        DEFINE FIELD IF NOT EXISTS actor       ON dag_run TYPE string;
        DEFINE FIELD IF NOT EXISTS trigger     ON dag_run TYPE string;
        DEFINE FIELD IF NOT EXISTS trigger_ref ON dag_run TYPE option<string>;
        DEFINE FIELD IF NOT EXISTS status      ON dag_run TYPE string;
        DEFINE FIELD IF NOT EXISTS started_at  ON dag_run TYPE datetime VALUE $before OR time::now();
        DEFINE FIELD IF NOT EXISTS ended_at    ON dag_run TYPE option<datetime>;

        -- Step state: per-formula execution state within a run.
        -- Written by the NATS formula event bridge; `workflow_id` + `formula_id` are string
        -- identifiers from the orchestrator, not record references.
        DEFINE TABLE IF NOT EXISTS step_state SCHEMAFULL;
        DEFINE FIELD IF NOT EXISTS workflow_id ON step_state TYPE string;
        DEFINE FIELD IF NOT EXISTS formula_id  ON step_state TYPE string;
        DEFINE FIELD IF NOT EXISTS status      ON step_state TYPE string;
        DEFINE FIELD IF NOT EXISTS started_at  ON step_state TYPE option<datetime>;
        DEFINE FIELD IF NOT EXISTS ended_at    ON step_state TYPE option<datetime>;
        DEFINE FIELD IF NOT EXISTS error       ON step_state TYPE option<string>;
        DEFINE INDEX IF NOT EXISTS step_wf_formula ON step_state FIELDS workflow_id, formula_id UNIQUE;
    "#,
    )
    .await
    .map_err(|e| Error::Migration(e.to_string()))?;
    info!(namespace = "infra", "tables ready");

    Ok(())
}
