//! Session database operations

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

/// Session record from database
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct DbSession {
    pub id: Uuid,
    pub machine_id: Option<Uuid>,
    pub started_at: DateTime<Utc>,
    pub ended_at: Option<DateTime<Utc>>,
    pub duration_secs: Option<i32>,
    pub is_support_session: bool,
    pub support_code: Option<String>,
    pub status: String,
    /// Tenancy-ready (Phase 4). Backfilled to the default tenant by migration 004.
    pub tenant_id: Option<Uuid>,
    /// True for managed/unattended (RMM) sessions; false for ad-hoc support.
    pub is_managed: bool,
    /// Origin of the session: 'standalone' (support code) | 'gururmm' (managed).
    pub source: String,
    /// Attended-consent state: 'not_required' | 'pending' | 'granted' | 'denied'.
    /// Enforcement is Task 5; this column carries the state only.
    pub consent_state: String,
}

/// Create a new session record
///
/// The consent/managed columns are derived from `is_support_session`:
///   - support session (attended): `is_managed = false`, `source = 'standalone'`,
///     `consent_state = 'pending'` — a viewer cannot be surfaced to the
///     technician until the end user accepts (Task 5).
///   - managed/persistent session: `is_managed = true`, `source = 'gururmm'`,
///     `consent_state = 'not_required'` (Phase-1 default policy).
pub async fn create_session(
    pool: &PgPool,
    session_id: Uuid,
    machine_id: Uuid,
    is_support_session: bool,
    support_code: Option<&str>,
) -> Result<DbSession, sqlx::Error> {
    let (is_managed, source, consent_state) = if is_support_session {
        (false, "standalone", "pending")
    } else {
        (true, "gururmm", "not_required")
    };

    sqlx::query_as::<_, DbSession>(
        r#"
        INSERT INTO connect_sessions
            (id, machine_id, is_support_session, support_code, status,
             is_managed, source, consent_state)
        VALUES ($1, $2, $3, $4, 'active', $5, $6, $7)
        RETURNING *
        "#,
    )
    .bind(session_id)
    .bind(machine_id)
    .bind(is_support_session)
    .bind(support_code)
    .bind(is_managed)
    .bind(source)
    .bind(consent_state)
    .fetch_one(pool)
    .await
}

/// Update the attended-consent state on a session row (Task 5).
///
/// `state` must be one of `not_required` | `pending` | `granted` | `denied`
/// (the column CHECK enforces this). Persists the consent decision so it is
/// durable and auditable alongside the `consent_granted`/`consent_denied`
/// events. Best-effort from the relay: a failure here does not change the
/// authoritative in-memory consent gate.
pub async fn update_consent_state(
    pool: &PgPool,
    session_id: Uuid,
    state: &str,
) -> Result<(), sqlx::Error> {
    sqlx::query(
        r#"
        UPDATE connect_sessions SET consent_state = $1 WHERE id = $2
        "#,
    )
    .bind(state)
    .bind(session_id)
    .execute(pool)
    .await?;
    Ok(())
}

/// End a session
pub async fn end_session(
    pool: &PgPool,
    session_id: Uuid,
    status: &str, // 'ended' or 'disconnected' or 'timeout'
) -> Result<(), sqlx::Error> {
    sqlx::query(
        r#"
        UPDATE connect_sessions SET
            ended_at = NOW(),
            duration_secs = EXTRACT(EPOCH FROM (NOW() - started_at))::INTEGER,
            status = $1
        WHERE id = $2
        "#,
    )
    .bind(status)
    .bind(session_id)
    .execute(pool)
    .await?;
    Ok(())
}

/// Get session by ID
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn get_session(
    pool: &PgPool,
    session_id: Uuid,
) -> Result<Option<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>("SELECT * FROM connect_sessions WHERE id = $1")
        .bind(session_id)
        .fetch_optional(pool)
        .await
}

/// Get active sessions for a machine
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn get_active_sessions_for_machine(
    pool: &PgPool,
    machine_id: Uuid,
) -> Result<Vec<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>(
        "SELECT * FROM connect_sessions WHERE machine_id = $1 AND status = 'active' ORDER BY started_at DESC"
    )
    .bind(machine_id)
    .fetch_all(pool)
    .await
}

/// Get recent sessions (for dashboard)
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn get_recent_sessions(pool: &PgPool, limit: i64) -> Result<Vec<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>(
        "SELECT * FROM connect_sessions ORDER BY started_at DESC LIMIT $1",
    )
    .bind(limit)
    .fetch_all(pool)
    .await
}

/// Get all sessions for a machine (for history export)
pub async fn get_sessions_for_machine(
    pool: &PgPool,
    machine_id: Uuid,
) -> Result<Vec<DbSession>, sqlx::Error> {
    sqlx::query_as::<_, DbSession>(
        "SELECT * FROM connect_sessions WHERE machine_id = $1 ORDER BY started_at DESC",
    )
    .bind(machine_id)
    .fetch_all(pool)
    .await
}
