//! Audit log browser — three tabs (conn / file / alarm), each capped at the
//! latest 200 rows. M5c MVP. Pagination/filtering by date range can come in
//! a follow-up if the operator outgrows this view.

use super::shared::{fmt_unix, html_escape, require_admin};
use crate::api::error::ApiError;
use crate::api::middleware::AuthedUser;
use crate::api::state::AppState;
use axum::extract::{Extension, Query};
use axum::response::Html;
use serde::Deserialize;
use std::fmt::Write as _;
use std::sync::Arc;

const PAGE_SIZE: i64 = 200;

#[derive(Debug, Deserialize)]
pub struct TabQuery {
    #[serde(default)]
    pub tab: Option<String>,
}

pub async fn index(
    Extension(state): Extension<Arc<AppState>>,
    admin: AuthedUser,
    Query(q): Query<TabQuery>,
) -> Result<Html<String>, ApiError> {
    require_admin(&admin)?;
    let tab = q.tab.as_deref().unwrap_or("conn");
    let body = match tab {
        "file" => render_file(&state).await?,
        "alarm" => render_alarm(&state).await?,
        _ => render_conn(&state).await?,
    };
    let pill = |id: &str, label: &str| {
        let active = id == tab;
        let cls = if active {
            "bg-slate-800 text-sky-300 border-sky-800"
        } else {
            "bg-slate-900 text-slate-400 border-slate-800 hover:text-slate-200"
        };
        format!(
            r##"<a href="#audit" hx-get="/admin/pages/audit?tab={id}" hx-target="#main" class="px-3 py-1 rounded border {cls}">{label}</a>"##,
            id = id,
            cls = cls,
            label = label,
        )
    };
    Ok(Html(format!(
        r##"<div class="space-y-4">
  <header class="flex items-center justify-between">
    <h2 class="text-lg font-semibold">Audit log</h2>
    <p class="text-xs text-slate-500">Latest {n} rows.</p>
  </header>
  <div class="flex gap-2 text-xs">{pill_conn}{pill_file}{pill_alarm}</div>
  {body}
</div>"##,
        n = PAGE_SIZE,
        pill_conn = pill("conn", "Connections"),
        pill_file = pill("file", "File transfers"),
        pill_alarm = pill("alarm", "Alarms"),
        body = body,
    )))
}

async fn render_conn(state: &Arc<AppState>) -> Result<String, ApiError> {
    let rows = state
        .db
        .audit_conn_list(PAGE_SIZE)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    if rows.is_empty() {
        return Ok(empty_table("No connection audit rows yet."));
    }
    let mut s = String::new();
    s.push_str(
        r##"<div class="rounded-md border border-slate-800 bg-slate-900 overflow-hidden">
  <table class="w-full text-sm">
    <thead class="text-xs uppercase text-slate-500 bg-slate-950"><tr>
      <th class="text-left font-medium px-3 py-2">When</th>
      <th class="text-left font-medium px-3 py-2">Peer</th>
      <th class="text-left font-medium px-3 py-2">Conn / Session</th>
      <th class="text-left font-medium px-3 py-2">IP</th>
      <th class="text-left font-medium px-3 py-2">Action</th>
      <th class="text-left font-medium px-3 py-2">Note</th>
    </tr></thead>
    <tbody class="divide-y divide-slate-800">"##,
    );
    for r in &rows {
        let _ = write!(
            s,
            r##"<tr>
  <td class="px-3 py-2 text-slate-500 text-xs">{when}</td>
  <td class="px-3 py-2 font-mono text-slate-200">{peer}</td>
  <td class="px-3 py-2 text-slate-400">{conn} / {sess}</td>
  <td class="px-3 py-2 text-slate-400 font-mono text-xs">{ip}</td>
  <td class="px-3 py-2 text-slate-300">{action}</td>
  <td class="px-3 py-2 text-slate-400">{note}</td>
</tr>"##,
            when = html_escape(&fmt_unix(r.started_at)),
            peer = html_escape(&r.peer_id),
            conn = r.conn_id,
            sess = r.session_id,
            ip = html_escape(&r.ip),
            action = html_escape(&r.action),
            note = html_escape(&r.note)
        );
    }
    s.push_str("</tbody></table></div>");
    Ok(s)
}

async fn render_file(state: &Arc<AppState>) -> Result<String, ApiError> {
    let rows = state
        .db
        .audit_file_list(PAGE_SIZE)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    if rows.is_empty() {
        return Ok(empty_table("No file-transfer audit rows yet."));
    }
    let mut s = String::new();
    s.push_str(
        r##"<div class="rounded-md border border-slate-800 bg-slate-900 overflow-hidden">
  <table class="w-full text-sm">
    <thead class="text-xs uppercase text-slate-500 bg-slate-950"><tr>
      <th class="text-left font-medium px-3 py-2">When</th>
      <th class="text-left font-medium px-3 py-2">Peer</th>
      <th class="text-left font-medium px-3 py-2">Direction</th>
      <th class="text-left font-medium px-3 py-2">Path</th>
      <th class="text-left font-medium px-3 py-2">Remote</th>
    </tr></thead>
    <tbody class="divide-y divide-slate-800">"##,
    );
    for r in &rows {
        let dir = match r.direction {
            0 => "→ remote",
            1 => "← remote",
            _ => "?",
        };
        let _ = write!(
            s,
            r##"<tr>
  <td class="px-3 py-2 text-slate-500 text-xs">{when}</td>
  <td class="px-3 py-2 font-mono text-slate-200">{peer}</td>
  <td class="px-3 py-2 text-slate-400">{dir}</td>
  <td class="px-3 py-2 text-slate-300 font-mono text-xs">{path}</td>
  <td class="px-3 py-2 text-slate-400 font-mono text-xs">{remote}</td>
</tr>"##,
            when = html_escape(&fmt_unix(r.at)),
            peer = html_escape(&r.peer_id),
            dir = dir,
            path = html_escape(&r.path),
            remote = html_escape(&r.remote_peer)
        );
    }
    s.push_str("</tbody></table></div>");
    Ok(s)
}

async fn render_alarm(state: &Arc<AppState>) -> Result<String, ApiError> {
    let rows = state
        .db
        .audit_alarm_list(PAGE_SIZE)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?;
    if rows.is_empty() {
        return Ok(empty_table("No alarm audit rows yet."));
    }
    let mut s = String::new();
    s.push_str(
        r##"<div class="rounded-md border border-slate-800 bg-slate-900 overflow-hidden">
  <table class="w-full text-sm">
    <thead class="text-xs uppercase text-slate-500 bg-slate-950"><tr>
      <th class="text-left font-medium px-3 py-2">When</th>
      <th class="text-left font-medium px-3 py-2">Peer</th>
      <th class="text-left font-medium px-3 py-2">Type</th>
      <th class="text-left font-medium px-3 py-2">Info</th>
    </tr></thead>
    <tbody class="divide-y divide-slate-800">"##,
    );
    for r in &rows {
        let typ = match r.typ {
            0 => "IpWhitelist",
            1 => "ExceedThirtyAttempts",
            2 => "SixAttemptsWithinOneMinute",
            6 => "ExceedIPv6PrefixAttempts",
            n => return Ok(format!("(unknown alarm type {})", n)),
        };
        let _ = write!(
            s,
            r##"<tr>
  <td class="px-3 py-2 text-slate-500 text-xs">{when}</td>
  <td class="px-3 py-2 font-mono text-slate-200">{peer}</td>
  <td class="px-3 py-2 text-amber-300">{typ}</td>
  <td class="px-3 py-2 text-slate-400 font-mono text-xs break-all">{info}</td>
</tr>"##,
            when = html_escape(&fmt_unix(r.at)),
            peer = html_escape(&r.peer_id),
            typ = typ,
            info = html_escape(&r.info_json)
        );
    }
    s.push_str("</tbody></table></div>");
    Ok(s)
}

fn empty_table(msg: &str) -> String {
    format!(
        r##"<div class="rounded-md border border-slate-800 bg-slate-900 p-6 text-center text-sm text-slate-500">{}</div>"##,
        html_escape(msg)
    )
}
