//! REST API endpoints

pub mod auth;
pub mod auth_logout;
pub mod changelog;
pub mod downloads;
pub mod machine_keys;
pub mod releases;
pub mod sessions;
pub mod users;

use axum::{
    extract::{Path, State},
    Json,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::db;
use crate::session::SessionManager;

/// Viewer info returned by API
#[derive(Debug, Serialize)]
pub struct ViewerInfoApi {
    pub id: String,
    pub name: String,
    pub connected_at: String,
}

impl From<crate::session::ViewerInfo> for ViewerInfoApi {
    fn from(v: crate::session::ViewerInfo) -> Self {
        Self {
            id: v.id,
            name: v.name,
            connected_at: v.connected_at.to_rfc3339(),
        }
    }
}

/// Session info returned by API
#[derive(Debug, Serialize)]
pub struct SessionInfo {
    pub id: String,
    pub agent_id: String,
    pub agent_name: String,
    pub started_at: String,
    pub viewer_count: usize,
    pub viewers: Vec<ViewerInfoApi>,
    pub is_streaming: bool,
    pub is_online: bool,
    pub is_persistent: bool,
    pub last_heartbeat: String,
    pub os_version: Option<String>,
    pub is_elevated: bool,
    pub uptime_secs: i64,
    pub display_count: i32,
    pub agent_version: Option<String>,
    /// Attended-consent state (Task 5): `not_required` | `pending` | `granted`
    /// | `denied`. The dashboard can surface "awaiting consent" for an attended
    /// session that has not yet been accepted on the managed machine.
    pub consent_state: String,
}

impl From<crate::session::Session> for SessionInfo {
    fn from(s: crate::session::Session) -> Self {
        Self {
            id: s.id.to_string(),
            agent_id: s.agent_id,
            agent_name: s.agent_name,
            started_at: s.started_at.to_rfc3339(),
            viewer_count: s.viewer_count,
            viewers: s.viewers.into_iter().map(ViewerInfoApi::from).collect(),
            is_streaming: s.is_streaming,
            is_online: s.is_online,
            is_persistent: s.is_persistent,
            last_heartbeat: s.last_heartbeat.to_rfc3339(),
            os_version: s.os_version,
            is_elevated: s.is_elevated,
            uptime_secs: s.uptime_secs,
            display_count: s.display_count,
            agent_version: s.agent_version,
            consent_state: s.consent_state.as_db_str().to_string(),
        }
    }
}

/// List all active sessions
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn list_sessions(State(sessions): State<SessionManager>) -> Json<Vec<SessionInfo>> {
    let sessions = sessions.list_sessions().await;
    Json(sessions.into_iter().map(SessionInfo::from).collect())
}

/// Get a specific 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(
    State(sessions): State<SessionManager>,
    Path(id): Path<String>,
) -> Result<Json<SessionInfo>, (axum::http::StatusCode, &'static str)> {
    let session_id = Uuid::parse_str(&id)
        .map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid session ID"))?;

    let session = sessions
        .get_session(session_id)
        .await
        .ok_or((axum::http::StatusCode::NOT_FOUND, "Session not found"))?;

    Ok(Json(SessionInfo::from(session)))
}

// ============================================================================
// Machine API Types
// ============================================================================

/// Machine info returned by API
#[derive(Debug, Serialize)]
pub struct MachineInfo {
    pub id: String,
    pub agent_id: String,
    pub hostname: String,
    pub os_version: Option<String>,
    pub is_elevated: bool,
    pub is_persistent: bool,
    pub first_seen: String,
    pub last_seen: String,
    pub status: String,
}

impl From<db::machines::Machine> for MachineInfo {
    fn from(m: db::machines::Machine) -> Self {
        Self {
            id: m.id.to_string(),
            agent_id: m.agent_id,
            hostname: m.hostname,
            os_version: m.os_version,
            is_elevated: m.is_elevated,
            is_persistent: m.is_persistent,
            first_seen: m.first_seen.to_rfc3339(),
            last_seen: m.last_seen.to_rfc3339(),
            status: m.status,
        }
    }
}

/// Session record for history
#[derive(Debug, Serialize)]
pub struct SessionRecord {
    pub id: String,
    pub started_at: String,
    pub ended_at: Option<String>,
    pub duration_secs: Option<i32>,
    pub is_support_session: bool,
    pub support_code: Option<String>,
    pub status: String,
}

impl From<db::sessions::DbSession> for SessionRecord {
    fn from(s: db::sessions::DbSession) -> Self {
        Self {
            id: s.id.to_string(),
            started_at: s.started_at.to_rfc3339(),
            ended_at: s.ended_at.map(|t| t.to_rfc3339()),
            duration_secs: s.duration_secs,
            is_support_session: s.is_support_session,
            support_code: s.support_code,
            status: s.status,
        }
    }
}

/// Event record for history
#[derive(Debug, Serialize)]
pub struct EventRecord {
    pub id: i64,
    pub session_id: String,
    pub event_type: String,
    pub timestamp: String,
    pub viewer_id: Option<String>,
    pub viewer_name: Option<String>,
    pub details: Option<serde_json::Value>,
    pub ip_address: Option<String>,
}

impl From<db::events::SessionEvent> for EventRecord {
    fn from(e: db::events::SessionEvent) -> Self {
        Self {
            id: e.id,
            session_id: e.session_id.to_string(),
            event_type: e.event_type,
            timestamp: e.timestamp.to_rfc3339(),
            viewer_id: e.viewer_id,
            viewer_name: e.viewer_name,
            details: e.details,
            ip_address: e.ip_address,
        }
    }
}

/// Full machine history (for export)
#[derive(Debug, Serialize)]
pub struct MachineHistory {
    pub machine: MachineInfo,
    pub sessions: Vec<SessionRecord>,
    pub events: Vec<EventRecord>,
    pub exported_at: String,
}

/// Query parameters for machine deletion
#[derive(Debug, Deserialize)]
pub struct DeleteMachineParams {
    /// If true, send uninstall command to agent (if online)
    #[serde(default)]
    pub uninstall: bool,
    /// If true, include history in response before deletion
    #[serde(default)]
    pub export: bool,
}

/// Response for machine deletion
#[derive(Debug, Serialize)]
pub struct DeleteMachineResponse {
    pub success: bool,
    pub message: String,
    pub uninstall_sent: bool,
    pub history: Option<MachineHistory>,
}
