// Admin-API for brukeradministrasjon (oppgave 064)
//
// Viser person-noder med roller, AI-budsjett, siste aktivitet.
// Støtter deaktivering/aktivering og oppdatering av AI-budsjett.

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

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

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

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

// =============================================================================
// GET /admin/users — brukeroversikt
// =============================================================================

#[derive(Serialize)]
pub struct UserInfo {
    pub node_id: Uuid,
    pub title: String,
    pub email: Option<String>,
    pub visibility: String,
    pub created_at: DateTime<Utc>,
    pub metadata: serde_json::Value,
    pub is_active: bool,
    pub roles: Vec<UserRole>,
    pub ai_budget: Option<AiBudget>,
    pub ai_usage_this_month: f64,
    pub last_activity: Option<DateTime<Utc>>,
}

#[derive(Serialize)]
pub struct UserRole {
    pub collection_id: Uuid,
    pub collection_title: String,
    pub role: String,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct AiBudget {
    pub monthly_limit_usd: f64,
}

#[derive(Serialize)]
pub struct UsersOverviewResponse {
    pub users: Vec<UserInfo>,
    pub total_count: i64,
}

pub async fn users_overview(
    State(state): State<AppState>,
    _admin: AdminUser,
) -> Result<Json<UsersOverviewResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Hent alle person-noder med auth-identitet
    #[derive(sqlx::FromRow)]
    struct UserRow {
        id: Uuid,
        title: Option<String>,
        visibility: String,
        metadata: serde_json::Value,
        created_at: DateTime<Utc>,
        email: Option<String>,
    }

    let user_rows = sqlx::query_as::<_, UserRow>(
        r#"SELECT n.id, n.title, n.visibility::text as visibility,
                  n.metadata, n.created_at,
                  ai.email
           FROM nodes n
           LEFT JOIN auth_identities ai ON ai.node_id = n.id
           WHERE n.node_kind = 'person'
           ORDER BY n.title NULLS LAST, n.created_at"#,
    )
    .fetch_all(&state.db)
    .await
    .map_err(|e| internal_error(&format!("Feil ved henting av brukere: {e}")))?;

    let total_count = user_rows.len() as i64;
    let mut users = Vec::new();

    for row in user_rows {
        // Roller: edges fra person → collection med type owner/admin/member_of
        #[derive(sqlx::FromRow)]
        struct RoleRow {
            collection_id: Uuid,
            collection_title: Option<String>,
            edge_type: String,
        }

        let roles = sqlx::query_as::<_, RoleRow>(
            r#"SELECT e.target_id as collection_id,
                      n.title as collection_title,
                      e.edge_type
               FROM edges e
               JOIN nodes n ON n.id = e.target_id AND n.node_kind = 'collection'
               WHERE e.source_id = $1
                 AND e.edge_type IN ('owner', 'admin', 'member_of')
               ORDER BY e.edge_type, n.title"#,
        )
        .bind(row.id)
        .fetch_all(&state.db)
        .await
        .map_err(|e| internal_error(&format!("DB-feil (roller): {e}")))?;

        // AI-budsjett fra metadata
        let ai_budget = row.metadata
            .get("ai_budget")
            .and_then(|b| serde_json::from_value::<AiBudget>(b.clone()).ok());

        // AI-forbruk denne måneden
        let (total_prompt, total_completion): (i64, i64) = sqlx::query_as::<_, (i64, i64)>(
            r#"SELECT
                COALESCE(SUM(prompt_tokens)::BIGINT, 0),
                COALESCE(SUM(completion_tokens)::BIGINT, 0)
               FROM ai_usage_log
               WHERE requested_by = $1
                 AND created_at >= date_trunc('month', now())"#,
        )
        .bind(row.id)
        .fetch_one(&state.db)
        .await
        .map_err(|e| internal_error(&format!("DB-feil (forbruk): {e}")))?;

        let ai_usage_this_month = crate::ai_budget::estimate_cost_usd(total_prompt, total_completion);

        // Siste aktivitet: nyeste created_at fra nodes opprettet av bruker
        let last_activity: Option<DateTime<Utc>> = sqlx::query_scalar(
            r#"SELECT MAX(created_at) FROM nodes WHERE created_by = $1"#,
        )
        .bind(row.id)
        .fetch_one(&state.db)
        .await
        .map_err(|e| internal_error(&format!("DB-feil (aktivitet): {e}")))?;

        // is_active: person med visibility != 'hidden' anses som aktiv
        let is_active = row.visibility != "hidden";

        users.push(UserInfo {
            node_id: row.id,
            title: row.title.unwrap_or_else(|| "Uten navn".to_string()),
            email: row.email,
            visibility: row.visibility,
            created_at: row.created_at,
            metadata: row.metadata,
            is_active,
            roles: roles.into_iter().map(|r| UserRole {
                collection_id: r.collection_id,
                collection_title: r.collection_title.unwrap_or_else(|| "Ukjent".to_string()),
                role: r.edge_type,
            }).collect(),
            ai_budget,
            ai_usage_this_month,
            last_activity,
        });
    }

    Ok(Json(UsersOverviewResponse { users, total_count }))
}

// =============================================================================
// POST /admin/users/toggle — aktiver/deaktiver bruker
// =============================================================================

#[derive(Deserialize)]
pub struct ToggleUserRequest {
    pub node_id: Uuid,
}

#[derive(Serialize)]
pub struct ToggleUserResponse {
    pub node_id: Uuid,
    pub is_active: bool,
}

pub async fn toggle_user(
    State(state): State<AppState>,
    _admin: AdminUser,
    Json(req): Json<ToggleUserRequest>,
) -> Result<Json<ToggleUserResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Sjekk at noden er en person
    let node_kind: Option<String> = sqlx::query_scalar(
        "SELECT node_kind FROM nodes WHERE id = $1",
    )
    .bind(req.node_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

    match node_kind.as_deref() {
        Some("person") => {}
        Some(_) => return Err((StatusCode::BAD_REQUEST, Json(ErrorResponse {
            error: "Noden er ikke en person".to_string(),
        }))),
        None => return Err((StatusCode::NOT_FOUND, Json(ErrorResponse {
            error: "Bruker finnes ikke".to_string(),
        }))),
    }

    // Toggle: hidden ↔ discoverable
    let current_visibility: String = sqlx::query_scalar(
        "SELECT visibility::text FROM nodes WHERE id = $1",
    )
    .bind(req.node_id)
    .fetch_one(&state.db)
    .await
    .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

    let new_visibility = if current_visibility == "hidden" { "discoverable" } else { "hidden" };

    sqlx::query(
        "UPDATE nodes SET visibility = $1::visibility WHERE id = $2",
    )
    .bind(new_visibility)
    .bind(req.node_id)
    .execute(&state.db)
    .await
    .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

    let is_active = new_visibility != "hidden";
    tracing::info!(user_node_id = %req.node_id, is_active, "Bruker-status endret");

    Ok(Json(ToggleUserResponse {
        node_id: req.node_id,
        is_active,
    }))
}

// =============================================================================
// POST /admin/users/budget — oppdater AI-budsjett
// =============================================================================

#[derive(Deserialize)]
pub struct UpdateBudgetRequest {
    pub node_id: Uuid,
    pub monthly_limit_usd: Option<f64>,
}

#[derive(Serialize)]
pub struct UpdateBudgetResponse {
    pub node_id: Uuid,
    pub ai_budget: Option<AiBudget>,
}

pub async fn update_budget(
    State(state): State<AppState>,
    _admin: AdminUser,
    Json(req): Json<UpdateBudgetRequest>,
) -> Result<Json<UpdateBudgetResponse>, (StatusCode, Json<ErrorResponse>)> {
    // Sjekk at noden er en person
    let node_kind: Option<String> = sqlx::query_scalar(
        "SELECT node_kind FROM nodes WHERE id = $1",
    )
    .bind(req.node_id)
    .fetch_optional(&state.db)
    .await
    .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

    if node_kind.as_deref() != Some("person") {
        return Err((StatusCode::BAD_REQUEST, Json(ErrorResponse {
            error: "Noden er ikke en person".to_string(),
        })));
    }

    match req.monthly_limit_usd {
        Some(limit) => {
            // Sett budsjett
            sqlx::query(
                r#"UPDATE nodes
                   SET metadata = jsonb_set(
                       COALESCE(metadata, '{}'::jsonb),
                       '{ai_budget}',
                       $1::jsonb
                   )
                   WHERE id = $2"#,
            )
            .bind(serde_json::json!({ "monthly_limit_usd": limit }))
            .bind(req.node_id)
            .execute(&state.db)
            .await
            .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

            tracing::info!(user_node_id = %req.node_id, limit, "AI-budsjett oppdatert");

            Ok(Json(UpdateBudgetResponse {
                node_id: req.node_id,
                ai_budget: Some(AiBudget { monthly_limit_usd: limit }),
            }))
        }
        None => {
            // Fjern budsjett
            sqlx::query(
                "UPDATE nodes SET metadata = metadata - 'ai_budget' WHERE id = $1",
            )
            .bind(req.node_id)
            .execute(&state.db)
            .await
            .map_err(|e| internal_error(&format!("DB-feil: {e}")))?;

            tracing::info!(user_node_id = %req.node_id, "AI-budsjett fjernet");

            Ok(Json(UpdateBudgetResponse {
                node_id: req.node_id,
                ai_budget: None,
            }))
        }
    }
}
