// Webhook-administrasjon (oppgave 29.5)
//
// Admin-API for å opprette, liste, regenerere token og slette webhooks.
// Webhooks er noder med node_kind='webhook', koblet til samlinger via
// belongs_to-edge. Token lagres i metadata.token.
//
// Ref: docs/features/universell_input.md (Webhook-seksjon)

use axum::{extract::State, http::StatusCode, Json};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::auth::AdminUser;
use crate::webhook_templates;
use crate::AppState;

// =============================================================================
// Datatyper
// =============================================================================

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

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

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

/// En webhook med info om målsamling og aktivitet.
#[derive(Serialize, sqlx::FromRow)]
pub struct WebhookInfo {
    pub id: Uuid,
    pub title: Option<String>,
    pub token: String,
    pub template_id: Option<String>,
    pub collection_id: Uuid,
    pub collection_title: Option<String>,
    pub created_at: DateTime<Utc>,
    pub event_count: i64,
    pub last_event_at: Option<DateTime<Utc>>,
}

/// Siste hendelser mottatt via en webhook.
#[derive(Serialize, sqlx::FromRow)]
pub struct WebhookEvent {
    pub node_id: Uuid,
    pub title: Option<String>,
    pub created_at: DateTime<Utc>,
    pub payload: Option<serde_json::Value>,
}

#[derive(Serialize)]
pub struct WebhooksOverviewResponse {
    pub webhooks: Vec<WebhookInfo>,
}

// =============================================================================
// GET /admin/webhooks — liste alle webhooks
// =============================================================================

pub async fn list_webhooks(
    State(state): State<AppState>,
    _admin: AdminUser,
) -> Result<Json<WebhooksOverviewResponse>, (StatusCode, Json<ErrorResponse>)> {
    let webhooks = sqlx::query_as::<_, WebhookInfo>(
        r#"SELECT
             w.id,
             w.title,
             w.metadata->>'token' AS token,
             w.metadata->>'template_id' AS template_id,
             e.target_id AS collection_id,
             c.title AS collection_title,
             w.created_at,
             COALESCE(ev.event_count, 0) AS event_count,
             ev.last_event_at
           FROM nodes w
           JOIN edges e ON e.source_id = w.id AND e.edge_type = 'belongs_to'
           JOIN nodes c ON c.id = e.target_id
           LEFT JOIN LATERAL (
             SELECT COUNT(*) AS event_count, MAX(n.created_at) AS last_event_at
             FROM nodes n
             WHERE n.node_kind = 'content'
               AND n.metadata->>'source' = 'webhook'
               AND n.metadata->>'webhook_id' = w.id::text
           ) ev ON true
           WHERE w.node_kind = 'webhook'
           ORDER BY w.created_at DESC"#,
    )
    .fetch_all(&state.db)
    .await
    .map_err(|e| internal_error(&format!("Feil ved henting av webhooks: {e}")))?;

    Ok(Json(WebhooksOverviewResponse { webhooks }))
}

// =============================================================================
// GET /admin/webhooks/events?webhook_id=... — siste hendelser for en webhook
// =============================================================================

#[derive(Deserialize)]
pub struct EventsQuery {
    pub webhook_id: Uuid,
    pub limit: Option<i64>,
}

pub async fn webhook_events(
    State(state): State<AppState>,
    _admin: AdminUser,
    axum::extract::Query(params): axum::extract::Query<EventsQuery>,
) -> Result<Json<Vec<WebhookEvent>>, (StatusCode, Json<ErrorResponse>)> {
    let limit = params.limit.unwrap_or(20).min(100);

    let events = sqlx::query_as::<_, WebhookEvent>(
        r#"SELECT
             n.id AS node_id,
             n.title,
             n.created_at,
             n.metadata->'payload' AS payload
           FROM nodes n
           WHERE n.node_kind = 'content'
             AND n.metadata->>'source' = 'webhook'
             AND n.metadata->>'webhook_id' = $1::text
           ORDER BY n.created_at DESC
           LIMIT $2"#,
    )
    .bind(params.webhook_id.to_string())
    .bind(limit)
    .fetch_all(&state.db)
    .await
    .map_err(|e| internal_error(&format!("Feil ved henting av webhook-hendelser: {e}")))?;

    Ok(Json(events))
}

// =============================================================================
// POST /admin/webhooks/create — opprett ny webhook
// =============================================================================

#[derive(Deserialize)]
pub struct CreateWebhookRequest {
    pub title: Option<String>,
    pub collection_id: Uuid,
    pub template_id: Option<String>,
}

#[derive(Serialize)]
pub struct CreateWebhookResponse {
    pub webhook_id: Uuid,
    pub token: Uuid,
}

pub async fn create_webhook(
    State(state): State<AppState>,
    admin: AdminUser,
    Json(req): Json<CreateWebhookRequest>,
) -> Result<Json<CreateWebhookResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Verifiser at samlingen eksisterer
    let collection_exists: Option<(Uuid,)> = sqlx::query_as(
        "SELECT id FROM nodes WHERE id = $1 AND node_kind = 'collection'",
    )
    .bind(req.collection_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

    if collection_exists.is_none() {
        return Err(bad_request("Samlingen finnes ikke"));
    }

    // Valider template_id hvis oppgitt
    if let Some(ref tmpl_id) = req.template_id {
        if webhook_templates::get_template(tmpl_id).is_none() {
            return Err(bad_request(&format!("Ukjent template: {tmpl_id}")));
        }
    }

    let webhook_id = Uuid::now_v7();
    let token = Uuid::now_v7();
    let title = req.title.unwrap_or_else(|| "Webhook".to_string());

    let mut metadata = serde_json::json!({
        "token": token.to_string(),
    });
    if let Some(ref tmpl_id) = req.template_id {
        metadata["template_id"] = serde_json::Value::String(tmpl_id.clone());
    }

    // Opprett webhook-node
    sqlx::query(
        r#"INSERT INTO nodes (id, node_kind, title, visibility, metadata, created_by)
           VALUES ($1, 'webhook', $2, 'hidden'::visibility, $3, $4)"#,
    )
    .bind(webhook_id)
    .bind(&title)
    .bind(&metadata)
    .bind(admin.node_id)
    .execute(&state.db)
    .await
    .map_err(|e| internal_error(&format!("Feil ved opprettelse av webhook: {e}")))?;

    // belongs_to-edge til samlingen
    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(webhook_id)
    .bind(req.collection_id)
    .bind(admin.node_id)
    .execute(&state.db)
    .await
    .map_err(|e| internal_error(&format!("Feil ved opprettelse av belongs_to-edge: {e}")))?;

    tracing::info!(
        webhook_id = %webhook_id,
        collection_id = %req.collection_id,
        "Webhook opprettet"
    );

    Ok(Json(CreateWebhookResponse { webhook_id, token }))
}

// =============================================================================
// POST /admin/webhooks/regenerate_token — generer nytt token
// =============================================================================

#[derive(Deserialize)]
pub struct RegenerateTokenRequest {
    pub webhook_id: Uuid,
}

#[derive(Serialize)]
pub struct RegenerateTokenResponse {
    pub new_token: Uuid,
}

pub async fn regenerate_token(
    State(state): State<AppState>,
    _admin: AdminUser,
    Json(req): Json<RegenerateTokenRequest>,
) -> Result<Json<RegenerateTokenResponse>, (StatusCode, Json<ErrorResponse>)> {
    let new_token = Uuid::now_v7();

    let result = sqlx::query(
        r#"UPDATE nodes
           SET metadata = jsonb_set(metadata, '{token}', to_jsonb($1::text)),
               updated_at = now()
           WHERE id = $2 AND node_kind = 'webhook'"#,
    )
    .bind(new_token.to_string())
    .bind(req.webhook_id)
    .execute(&state.db)
    .await
    .map_err(|e| internal_error(&format!("Feil ved regenerering av token: {e}")))?;

    if result.rows_affected() == 0 {
        return Err(bad_request("Webhook finnes ikke"));
    }

    tracing::info!(webhook_id = %req.webhook_id, "Webhook-token regenerert");

    Ok(Json(RegenerateTokenResponse { new_token }))
}

// =============================================================================
// POST /admin/webhooks/delete — slett webhook
// =============================================================================

#[derive(Deserialize)]
pub struct DeleteWebhookRequest {
    pub webhook_id: Uuid,
}

#[derive(Serialize)]
pub struct DeleteWebhookResponse {
    pub deleted: bool,
}

pub async fn delete_webhook(
    State(state): State<AppState>,
    _admin: AdminUser,
    Json(req): Json<DeleteWebhookRequest>,
) -> Result<Json<DeleteWebhookResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Slett edges først (belongs_to fra webhook til samling)
    sqlx::query("DELETE FROM edges WHERE source_id = $1 OR target_id = $1")
        .bind(req.webhook_id)
        .execute(&state.db)
        .await
        .map_err(|e| internal_error(&format!("Feil ved sletting av edges: {e}")))?;

    // Slett selve webhook-noden
    let result = sqlx::query("DELETE FROM nodes WHERE id = $1 AND node_kind = 'webhook'")
        .bind(req.webhook_id)
        .execute(&state.db)
        .await
        .map_err(|e| internal_error(&format!("Feil ved sletting av webhook: {e}")))?;

    tracing::info!(webhook_id = %req.webhook_id, "Webhook slettet");

    Ok(Json(DeleteWebhookResponse {
        deleted: result.rows_affected() > 0,
    }))
}

// =============================================================================
// GET /admin/webhooks/templates — liste tilgjengelige templates
// =============================================================================

pub async fn list_templates(
    _admin: AdminUser,
) -> Json<Vec<webhook_templates::TemplateInfo>> {
    Json(webhook_templates::list_templates())
}

// =============================================================================
// POST /admin/webhooks/set_template — sett eller fjern template på webhook
// =============================================================================

#[derive(Deserialize)]
pub struct SetTemplateRequest {
    pub webhook_id: Uuid,
    /// None eller tom streng fjerner template.
    pub template_id: Option<String>,
}

#[derive(Serialize)]
pub struct SetTemplateResponse {
    pub template_id: Option<String>,
}

pub async fn set_template(
    State(state): State<AppState>,
    _admin: AdminUser,
    Json(req): Json<SetTemplateRequest>,
) -> Result<Json<SetTemplateResponse>, (StatusCode, Json<ErrorResponse>)> {
    let tmpl_id = req.template_id.filter(|s| !s.is_empty());

    // Valider template_id
    if let Some(ref id) = tmpl_id {
        if webhook_templates::get_template(id).is_none() {
            return Err(bad_request(&format!("Ukjent template: {id}")));
        }
    }

    // Oppdater metadata: sett eller fjern template_id
    let (query, effective_id) = if let Some(ref id) = tmpl_id {
        (
            r#"UPDATE nodes
               SET metadata = jsonb_set(metadata, '{template_id}', to_jsonb($1::text)),
                   updated_at = now()
               WHERE id = $2 AND node_kind = 'webhook'"#,
            id.clone(),
        )
    } else {
        (
            r#"UPDATE nodes
               SET metadata = metadata - 'template_id',
                   updated_at = now()
               WHERE id = $2 AND node_kind = 'webhook'"#,
            String::new(),
        )
    };

    let result = sqlx::query(query)
        .bind(&effective_id)
        .bind(req.webhook_id)
        .execute(&state.db)
        .await
        .map_err(|e| internal_error(&format!("Feil ved oppdatering av template: {e}")))?;

    if result.rows_affected() == 0 {
        return Err(bad_request("Webhook finnes ikke"));
    }

    tracing::info!(
        webhook_id = %req.webhook_id,
        template_id = ?tmpl_id,
        "Webhook-template oppdatert"
    );

    Ok(Json(SetTemplateResponse { template_id: tmpl_id }))
}
