// Webhook-endepunkt: POST /api/webhook/<token>
//
// Offentlig endepunkt (ingen JWT). Validerer token mot en webhook-node
// i databasen, oppretter content-node med payload i metadata, og kobler
// den til målsamlingen via belongs_to-edge.
//
// Ref: docs/features/universell_input.md (Webhook-seksjon)

use axum::{
    extract::{Path, State},
    http::StatusCode,
    Json,
};
use serde::Serialize;
use uuid::Uuid;

use crate::webhook_templates;
use crate::AppState;

#[derive(Serialize)]
pub struct ErrorResponse {
    pub error: String,
}

#[derive(Serialize)]
pub struct WebhookResponse {
    pub node_id: Uuid,
    pub edge_id: Uuid,
}

fn not_found(msg: &str) -> (StatusCode, Json<ErrorResponse>) {
    (
        StatusCode::NOT_FOUND,
        Json(ErrorResponse { error: msg.to_string() }),
    )
}

fn internal_error(msg: &str) -> (StatusCode, Json<ErrorResponse>) {
    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Json(ErrorResponse { error: msg.to_string() }),
    )
}

/// Rad fra token-oppslag: webhook-nodens id, målsamling og evt. template.
#[derive(sqlx::FromRow)]
struct WebhookLookup {
    webhook_id: Uuid,
    collection_id: Uuid,
    template_id: Option<String>,
}

/// POST /api/webhook/<token>
///
/// Mottar vilkårlig JSON-body og oppretter en content-node med payload
/// i metadata. Noden knyttes til webhook-nodens målsamling via belongs_to-edge.
pub async fn handle_webhook(
    State(state): State<AppState>,
    Path(token): Path<Uuid>,
    body: Option<Json<serde_json::Value>>,
) -> Result<Json<WebhookResponse>, (StatusCode, Json<ErrorResponse>)> {
    // -- Slå opp webhook-node via token --
    // Token lagres i metadata.token på en webhook-node.
    // Målsamlingen finnes via belongs_to-edge fra webhook-noden.
    let lookup = sqlx::query_as::<_, WebhookLookup>(
        r#"SELECT w.id AS webhook_id, e.target_id AS collection_id,
                  w.metadata->>'template_id' AS template_id
           FROM nodes w
           JOIN edges e ON e.source_id = w.id AND e.edge_type = 'belongs_to'
           WHERE w.node_kind = 'webhook'
             AND w.metadata->>'token' = $1::text"#,
    )
    .bind(token.to_string())
    .fetch_optional(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil ved webhook-token-oppslag: {e}");
        internal_error("Databasefeil")
    })?;

    let lookup = match lookup {
        Some(l) => l,
        None => return Err(not_found("Ugyldig webhook-token")),
    };

    // -- Payload --
    let payload = body
        .map(|Json(v)| v)
        .unwrap_or(serde_json::json!({}));

    // -- Ekstraher title/content via template eller generisk --
    let (title, content, extra_meta) = if let Some(ref tmpl_id) = lookup.template_id {
        if let Some(result) = webhook_templates::apply_template(tmpl_id, &payload) {
            (result.title, result.content, Some(result.extra_metadata))
        } else {
            tracing::warn!(template_id = %tmpl_id, "Ukjent webhook-template, bruker generisk");
            extract_generic(&payload)
        }
    } else {
        extract_generic(&payload)
    };

    // -- Opprett content-node --
    let node_id = Uuid::now_v7();
    let mut metadata = serde_json::json!({
        "source": "webhook",
        "webhook_id": lookup.webhook_id.to_string(),
        "payload": payload,
    });
    // Merg inn ekstra metadata fra template
    if let Some(extra) = extra_meta {
        if let Some(obj) = extra.as_object() {
            for (k, v) in obj {
                metadata[k.clone()] = v.clone();
            }
        }
    }

    sqlx::query(
        r#"INSERT INTO nodes (id, node_kind, title, content, visibility, metadata, created_by)
           VALUES ($1, 'content', NULLIF($2, ''), NULLIF($3, ''), 'hidden'::visibility, $4, $5)"#,
    )
    .bind(node_id)
    .bind(&title)
    .bind(&content)
    .bind(&metadata)
    .bind(lookup.webhook_id)
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil ved opprettelse av webhook-node: {e}");
        internal_error("Databasefeil ved opprettelse av node")
    })?;

    // -- belongs_to-edge til målsamling --
    let edge_id = Uuid::now_v7();
    sqlx::query(
        r#"INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by)
           VALUES ($1, $2, $3, 'belongs_to', '{}'::jsonb, true, $4)"#,
    )
    .bind(edge_id)
    .bind(node_id)
    .bind(lookup.collection_id)
    .bind(lookup.webhook_id)
    .execute(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("PG-feil ved opprettelse av belongs_to-edge: {e}");
        internal_error("Databasefeil ved opprettelse av edge")
    })?;

    // -- Propager tilgang fra samlingen til den nye noden --
    if let Err(e) = sqlx::query("SELECT propagate_belongs_to_access($1, $2)")
        .bind(node_id)
        .bind(lookup.collection_id)
        .execute(&state.db)
        .await
    {
        tracing::error!("propagate_belongs_to_access feilet for webhook-node: {e}");
    }

    tracing::info!(
        node_id = %node_id,
        edge_id = %edge_id,
        webhook_id = %lookup.webhook_id,
        collection_id = %lookup.collection_id,
        "Webhook mottatt — content-node opprettet"
    );

    Ok(Json(WebhookResponse { node_id, edge_id }))
}

/// Generisk ekstraksjon: prøv title og content/body-felt.
fn extract_generic(payload: &serde_json::Value) -> (String, String, Option<serde_json::Value>) {
    let title = payload
        .get("title")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let content = payload
        .get("content")
        .or_else(|| payload.get("body"))
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    (title, content, None)
}
