//! Support code database operations

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

/// Support code record from database
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub struct DbSupportCode {
    pub id: Uuid,
    pub code: String,
    pub session_id: Option<Uuid>,
    pub created_by: String,
    pub created_at: DateTime<Utc>,
    pub expires_at: Option<DateTime<Utc>>,
    pub status: String,
    pub client_name: Option<String>,
    pub client_machine: Option<String>,
    pub connected_at: Option<DateTime<Utc>>,
    /// Single-use marker. Set when a code is consumed on first agent bind.
    /// Consume logic is Task 4; this column carries the marker only.
    pub consumed_at: Option<DateTime<Utc>>,
    /// Tenancy-ready (Phase 4). Backfilled to the default tenant by migration 004.
    pub tenant_id: Option<Uuid>,
}

/// Create a new support code
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn create_support_code(
    pool: &PgPool,
    code: &str,
    created_by: &str,
) -> Result<DbSupportCode, sqlx::Error> {
    sqlx::query_as::<_, DbSupportCode>(
        r#"
        INSERT INTO connect_support_codes (code, created_by, status)
        VALUES ($1, $2, 'pending')
        RETURNING *
        "#,
    )
    .bind(code)
    .bind(created_by)
    .fetch_one(pool)
    .await
}

/// Get support code by code string
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn get_support_code(
    pool: &PgPool,
    code: &str,
) -> Result<Option<DbSupportCode>, sqlx::Error> {
    sqlx::query_as::<_, DbSupportCode>("SELECT * FROM connect_support_codes WHERE code = $1")
        .bind(code)
        .fetch_optional(pool)
        .await
}

/// Update support code when client connects.
///
/// Superseded by [`consume_code_for_bind`] for the single-use bind path (Task 4);
/// retained for any non-bind callers / future managed-session flows.
#[allow(dead_code)]
pub async fn mark_code_connected(
    pool: &PgPool,
    code: &str,
    session_id: Option<Uuid>,
    client_name: Option<&str>,
    client_machine: Option<&str>,
) -> Result<(), sqlx::Error> {
    sqlx::query(
        r#"
        UPDATE connect_support_codes SET
            status = 'connected',
            session_id = $1,
            client_name = $2,
            client_machine = $3,
            connected_at = NOW()
        WHERE code = $4
        "#,
    )
    .bind(session_id)
    .bind(client_name)
    .bind(client_machine)
    .bind(code)
    .execute(pool)
    .await?;
    Ok(())
}

/// Atomically CONSUME a support code for a first-time agent bind (single-use).
///
/// This is the durable single-use guarantee (Task 4 — closes the reusable-code
/// HIGH). It is a single conditional UPDATE: the row is updated ONLY if the code
/// has never been consumed (`consumed_at IS NULL`) AND is still `pending`. On
/// success it stamps `consumed_at = NOW()`, flips `status` to `'connected'`, and
/// records the binding session/client, returning the row's id.
///
/// If ZERO rows match (code already consumed, already connected, completed,
/// cancelled, expired, or nonexistent) it returns `Ok(None)` — the caller MUST
/// reject the bind. Because the guard is in the `WHERE` clause, two concurrent
/// presenters of the same code cannot both succeed: PostgreSQL serializes the
/// row update, so the second sees `consumed_at` already set (or status no longer
/// `pending`) and matches zero rows.
///
/// Returns `Ok(Some(id))` if THIS call consumed the code, `Ok(None)` if it was
/// not available for consumption.
pub async fn consume_code_for_bind(
    pool: &PgPool,
    code: &str,
    session_id: Option<Uuid>,
    client_name: Option<&str>,
    client_machine: Option<&str>,
) -> Result<Option<Uuid>, sqlx::Error> {
    // The expiry guard mirrors the existing validity semantics: a code with an
    // `expires_at` in the past is not consumable. `expires_at IS NULL` means no
    // expiry was set (legacy/ad-hoc codes) and is allowed.
    let id: Option<Uuid> = sqlx::query_scalar(
        r#"
        UPDATE connect_support_codes
        SET status = 'connected',
            consumed_at = NOW(),
            session_id = $1,
            client_name = $2,
            client_machine = $3,
            connected_at = NOW()
        WHERE code = $4
          AND consumed_at IS NULL
          AND status = 'pending'
          AND (expires_at IS NULL OR expires_at > NOW())
        RETURNING id
        "#,
    )
    .bind(session_id)
    .bind(client_name)
    .bind(client_machine)
    .bind(code)
    .fetch_optional(pool)
    .await?;

    Ok(id)
}

/// Mark support code as completed
pub async fn mark_code_completed(pool: &PgPool, code: &str) -> Result<(), sqlx::Error> {
    sqlx::query("UPDATE connect_support_codes SET status = 'completed' WHERE code = $1")
        .bind(code)
        .execute(pool)
        .await?;
    Ok(())
}

/// Mark support code as cancelled
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn mark_code_cancelled(pool: &PgPool, code: &str) -> Result<(), sqlx::Error> {
    sqlx::query("UPDATE connect_support_codes SET status = 'cancelled' WHERE code = $1")
        .bind(code)
        .execute(pool)
        .await?;
    Ok(())
}

/// Get active support codes (pending or connected)
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn get_active_support_codes(pool: &PgPool) -> Result<Vec<DbSupportCode>, sqlx::Error> {
    sqlx::query_as::<_, DbSupportCode>(
        "SELECT * FROM connect_support_codes WHERE status IN ('pending', 'connected') ORDER BY created_at DESC"
    )
    .fetch_all(pool)
    .await
}

/// Check if code exists and is valid for connection
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn is_code_valid(pool: &PgPool, code: &str) -> Result<bool, sqlx::Error> {
    let result = sqlx::query_scalar::<_, bool>(
        "SELECT EXISTS(SELECT 1 FROM connect_support_codes WHERE code = $1 AND status = 'pending')",
    )
    .bind(code)
    .fetch_one(pool)
    .await?;
    Ok(result)
}

/// Check if code is cancelled
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn is_code_cancelled(pool: &PgPool, code: &str) -> Result<bool, sqlx::Error> {
    let result = sqlx::query_scalar::<_, bool>(
        "SELECT EXISTS(SELECT 1 FROM connect_support_codes WHERE code = $1 AND status = 'cancelled')"
    )
    .bind(code)
    .fetch_one(pool)
    .await?;
    Ok(result)
}

/// Link session to support code
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn link_session_to_code(
    pool: &PgPool,
    code: &str,
    session_id: Uuid,
) -> Result<(), sqlx::Error> {
    sqlx::query("UPDATE connect_support_codes SET session_id = $1 WHERE code = $2")
        .bind(session_id)
        .bind(code)
        .execute(pool)
        .await?;
    Ok(())
}
