//! Bot API token persistence. `token_hash` is sha256 of the raw bearer
//! token; the raw value is shown to the user once at creation and never
//! stored.

use chrono::{DateTime, Utc};
use sqlx::PgPool;
use uuid::Uuid;

use crate::domain::ApiToken;
use crate::error::AppResult;

pub async fn create(
    pool: &PgPool,
    user_id: Uuid,
    name: &str,
    token_hash: &[u8],
    expires_at: Option<DateTime<Utc>>,
) -> AppResult<ApiToken> {
    let row = sqlx::query_as::<_, ApiToken>(
        r#"
        INSERT INTO api_tokens (user_id, name, token_hash, expires_at)
        VALUES ($1, $2, $3, $4)
        RETURNING id, user_id, name, token_hash, created_at, last_used_at, expires_at
        "#,
    )
    .bind(user_id)
    .bind(name)
    .bind(token_hash)
    .bind(expires_at)
    .fetch_one(pool)
    .await?;
    Ok(row)
}

pub async fn find_active(pool: &PgPool, token_hash: &[u8]) -> AppResult<Option<ApiToken>> {
    let row = sqlx::query_as::<_, ApiToken>(
        r#"
        SELECT id, user_id, name, token_hash, created_at, last_used_at, expires_at
        FROM api_tokens
        WHERE token_hash = $1
          AND (expires_at IS NULL OR expires_at > now())
        "#,
    )
    .bind(token_hash)
    .fetch_optional(pool)
    .await?;
    Ok(row)
}

pub async fn touch_last_used(pool: &PgPool, id: Uuid) -> AppResult<()> {
    sqlx::query("UPDATE api_tokens SET last_used_at = now() WHERE id = $1")
        .bind(id)
        .execute(pool)
        .await?;
    Ok(())
}

pub async fn find_owner(pool: &PgPool, id: Uuid) -> AppResult<Option<Uuid>> {
    let row: Option<(Uuid,)> =
        sqlx::query_as("SELECT user_id FROM api_tokens WHERE id = $1")
            .bind(id)
            .fetch_optional(pool)
            .await?;
    Ok(row.map(|(uid,)| uid))
}

pub async fn delete(pool: &PgPool, id: Uuid) -> AppResult<()> {
    sqlx::query("DELETE FROM api_tokens WHERE id = $1")
        .bind(id)
        .execute(pool)
        .await?;
    Ok(())
}
