// Brukersynlig forbruk — oppgave 15.9
//
// To endepunkter:
//   GET /my/usage?days=30       — innlogget brukers eget forbruk
//   GET /query/node_usage?node_id=<uuid>&days=30 — forbruk for en spesifikk node (kun eier)
//
// Bygger på resource_usage_log (oppgave 15.7) med filtrering på
// triggered_by (bruker) og target_node_id (node).
//
// Ref: docs/features/ressursforbruk.md

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

use crate::auth::AuthUser;
use crate::AppState;

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

/// Aggregert forbruk per ressurstype for én bruker eller node.
#[derive(Serialize, sqlx::FromRow)]
pub struct ResourceTypeSummary {
    pub resource_type: String,
    pub event_count: i64,
    pub total_value: f64,
    pub secondary_value: f64,
}

/// Daglig tidsserie for bruker/node.
#[derive(Serialize, sqlx::FromRow)]
pub struct DailyUsage {
    pub day: chrono::DateTime<chrono::Utc>,
    pub resource_type: String,
    pub event_count: i64,
    pub total_value: f64,
}

/// Grafstatistikk: noder og edges opprettet.
#[derive(Serialize)]
pub struct GraphStats {
    pub nodes_created: i64,
    pub edges_created: i64,
}

/// Samlet respons for brukerforbruk.
#[derive(Serialize)]
pub struct UserUsageResponse {
    pub by_type: Vec<ResourceTypeSummary>,
    pub daily: Vec<DailyUsage>,
    pub graph: GraphStats,
}

/// Samlet respons for node-forbruk.
#[derive(Serialize)]
pub struct NodeUsageResponse {
    pub node_id: Uuid,
    pub node_title: Option<String>,
    pub by_type: Vec<ResourceTypeSummary>,
    pub daily: Vec<DailyUsage>,
}

#[derive(Deserialize)]
pub struct UsageParams {
    pub days: Option<i32>,
}

#[derive(Deserialize)]
pub struct NodeUsageParams {
    pub node_id: Uuid,
    pub days: Option<i32>,
}

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

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

// =============================================================================
// GET /my/usage — brukerens eget forbruk
// =============================================================================

pub async fn my_usage(
    State(state): State<AppState>,
    user: AuthUser,
    axum::extract::Query(params): axum::extract::Query<UsageParams>,
) -> Result<Json<UserUsageResponse>, (StatusCode, Json<ErrorResponse>)> {
    let days = params.days.unwrap_or(30).clamp(1, 365);
    let user_id = user.node_id;

    let by_type = fetch_user_by_type(&state.db, user_id, days)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?;

    let daily = fetch_user_daily(&state.db, user_id, days)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?;

    let graph = fetch_graph_stats_for_user(&state.db, user_id)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?;

    Ok(Json(UserUsageResponse { by_type, daily, graph }))
}

// =============================================================================
// GET /query/node_usage — forbruk for en spesifikk node
// =============================================================================

pub async fn node_usage(
    State(state): State<AppState>,
    user: AuthUser,
    axum::extract::Query(params): axum::extract::Query<NodeUsageParams>,
) -> Result<Json<NodeUsageResponse>, (StatusCode, Json<ErrorResponse>)> {
    let days = params.days.unwrap_or(30).clamp(1, 365);
    let node_id = params.node_id;

    // Sjekk at brukeren eier noden (created_by) eller har owner/admin-edge
    let is_owner = check_node_access(&state.db, node_id, user.node_id)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?;

    if !is_owner {
        return Err(err(StatusCode::FORBIDDEN, "Du har ikke tilgang til denne nodens forbruk"));
    }

    let node_title: Option<String> = sqlx::query_scalar("SELECT title FROM nodes WHERE id = $1")
        .bind(node_id)
        .fetch_optional(&state.db)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?
        .flatten();

    let by_type = fetch_node_by_type(&state.db, node_id, days)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?;

    let daily = fetch_node_daily(&state.db, node_id, days)
        .await
        .map_err(|e| err(StatusCode::INTERNAL_SERVER_ERROR, &format!("DB-feil: {e}")))?;

    Ok(Json(NodeUsageResponse { node_id, node_title, by_type, daily }))
}

// =============================================================================
// Tilgangssjekk
// =============================================================================

/// Bruker har tilgang til nodeforbruk hvis:
/// 1. De opprettet noden (created_by), eller
/// 2. De har en owner- eller admin-edge til noden
async fn check_node_access(db: &PgPool, node_id: Uuid, user_id: Uuid) -> Result<bool, sqlx::Error> {
    let row: Option<(bool,)> = sqlx::query_as(
        r#"
        SELECT EXISTS(
            SELECT 1 FROM nodes WHERE id = $1 AND created_by = $2
            UNION ALL
            SELECT 1 FROM edges WHERE target_node_id = $1 AND source_node_id = $2
                AND edge_type IN ('owner', 'admin')
        ) AS ok
        "#,
    )
    .bind(node_id)
    .bind(user_id)
    .fetch_optional(db)
    .await?;

    Ok(row.map(|r| r.0).unwrap_or(false))
}

// =============================================================================
// Spørringer — brukerforbruk
// =============================================================================

async fn fetch_user_by_type(
    db: &PgPool,
    user_id: Uuid,
    days: i32,
) -> Result<Vec<ResourceTypeSummary>, sqlx::Error> {
    sqlx::query_as::<_, ResourceTypeSummary>(
        r#"
        SELECT
            r.resource_type,
            COUNT(*)::BIGINT AS event_count,
            COALESCE(SUM(
                CASE r.resource_type
                    WHEN 'ai'        THEN (r.detail->>'tokens_in')::FLOAT8
                    WHEN 'whisper'   THEN (r.detail->>'duration_seconds')::FLOAT8
                    WHEN 'tts'       THEN (r.detail->>'characters')::FLOAT8
                    WHEN 'cas'       THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'bandwidth' THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'livekit'   THEN (r.detail->>'participant_minutes')::FLOAT8
                    ELSE 0
                END
            ), 0) AS total_value,
            COALESCE(SUM(
                CASE r.resource_type
                    WHEN 'ai' THEN (r.detail->>'tokens_out')::FLOAT8
                    ELSE 0
                END
            ), 0) AS secondary_value
        FROM resource_usage_log r
        WHERE r.triggered_by = $1
          AND r.created_at >= now() - make_interval(days := $2)
        GROUP BY r.resource_type
        ORDER BY total_value DESC
        "#,
    )
    .bind(user_id)
    .bind(days)
    .fetch_all(db)
    .await
}

async fn fetch_user_daily(
    db: &PgPool,
    user_id: Uuid,
    days: i32,
) -> Result<Vec<DailyUsage>, sqlx::Error> {
    sqlx::query_as::<_, DailyUsage>(
        r#"
        SELECT
            date_trunc('day', r.created_at) AS day,
            r.resource_type,
            COUNT(*)::BIGINT AS event_count,
            COALESCE(SUM(
                CASE r.resource_type
                    WHEN 'ai'        THEN (r.detail->>'tokens_in')::FLOAT8
                    WHEN 'whisper'   THEN (r.detail->>'duration_seconds')::FLOAT8
                    WHEN 'tts'       THEN (r.detail->>'characters')::FLOAT8
                    WHEN 'cas'       THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'bandwidth' THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'livekit'   THEN (r.detail->>'participant_minutes')::FLOAT8
                    ELSE 0
                END
            ), 0) AS total_value
        FROM resource_usage_log r
        WHERE r.triggered_by = $1
          AND r.created_at >= now() - make_interval(days := $2)
        GROUP BY date_trunc('day', r.created_at), r.resource_type
        ORDER BY day DESC, resource_type
        "#,
    )
    .bind(user_id)
    .bind(days)
    .fetch_all(db)
    .await
}

async fn fetch_graph_stats_for_user(db: &PgPool, user_id: Uuid) -> Result<GraphStats, sqlx::Error> {
    let nodes: (i64,) = sqlx::query_as("SELECT COUNT(*)::BIGINT FROM nodes WHERE created_by = $1")
        .bind(user_id)
        .fetch_one(db)
        .await?;

    let edges: (i64,) = sqlx::query_as(
        "SELECT COUNT(*)::BIGINT FROM edges WHERE created_by = $1",
    )
    .bind(user_id)
    .fetch_one(db)
    .await?;

    Ok(GraphStats {
        nodes_created: nodes.0,
        edges_created: edges.0,
    })
}

// =============================================================================
// Spørringer — nodeforbruk
// =============================================================================

async fn fetch_node_by_type(
    db: &PgPool,
    node_id: Uuid,
    days: i32,
) -> Result<Vec<ResourceTypeSummary>, sqlx::Error> {
    sqlx::query_as::<_, ResourceTypeSummary>(
        r#"
        SELECT
            r.resource_type,
            COUNT(*)::BIGINT AS event_count,
            COALESCE(SUM(
                CASE r.resource_type
                    WHEN 'ai'        THEN (r.detail->>'tokens_in')::FLOAT8
                    WHEN 'whisper'   THEN (r.detail->>'duration_seconds')::FLOAT8
                    WHEN 'tts'       THEN (r.detail->>'characters')::FLOAT8
                    WHEN 'cas'       THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'bandwidth' THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'livekit'   THEN (r.detail->>'participant_minutes')::FLOAT8
                    ELSE 0
                END
            ), 0) AS total_value,
            COALESCE(SUM(
                CASE r.resource_type
                    WHEN 'ai' THEN (r.detail->>'tokens_out')::FLOAT8
                    ELSE 0
                END
            ), 0) AS secondary_value
        FROM resource_usage_log r
        WHERE r.target_node_id = $1
          AND r.created_at >= now() - make_interval(days := $2)
        GROUP BY r.resource_type
        ORDER BY total_value DESC
        "#,
    )
    .bind(node_id)
    .bind(days)
    .fetch_all(db)
    .await
}

async fn fetch_node_daily(
    db: &PgPool,
    node_id: Uuid,
    days: i32,
) -> Result<Vec<DailyUsage>, sqlx::Error> {
    sqlx::query_as::<_, DailyUsage>(
        r#"
        SELECT
            date_trunc('day', r.created_at) AS day,
            r.resource_type,
            COUNT(*)::BIGINT AS event_count,
            COALESCE(SUM(
                CASE r.resource_type
                    WHEN 'ai'        THEN (r.detail->>'tokens_in')::FLOAT8
                    WHEN 'whisper'   THEN (r.detail->>'duration_seconds')::FLOAT8
                    WHEN 'tts'       THEN (r.detail->>'characters')::FLOAT8
                    WHEN 'cas'       THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'bandwidth' THEN (r.detail->>'size_bytes')::FLOAT8
                    WHEN 'livekit'   THEN (r.detail->>'participant_minutes')::FLOAT8
                    ELSE 0
                END
            ), 0) AS total_value
        FROM resource_usage_log r
        WHERE r.target_node_id = $1
          AND r.created_at >= now() - make_interval(days := $2)
        GROUP BY date_trunc('day', r.created_at), r.resource_type
        ORDER BY day DESC, resource_type
        "#,
    )
    .bind(node_id)
    .bind(days)
    .fetch_all(db)
    .await
}
