//! Legacy [`AuditLog`] API. Slice 2 of item 1 makes this a thin
//! façade over [`SqliteSessionLog`] — see
//! `docs/managed-agents-parity.md` for the full design.
//!
//! The public surface is unchanged for the existing CLI commands
//! (`wirken audit log`, `wirken audit verify`) and for the
//! [`AuditWriter`] flush task. Internally:
//!
//! - `audit_events` is no longer a real table — it is a SQL view
//!   over `session_events` defined in [`crate::legacy_compat`].
//! - `write_batch` converts each [`AuditEvent`] into a
//!   [`SessionEvent::AuditLegacy`] and routes through
//!   [`SqliteSessionLog::append_with_ts`].
//! - `query` reads through the view.
//! - `verify` walks every per-session chain in `session_events` and
//!   reports an aggregate result.
//! - `prune` deletes rows older than the retention window from each
//!   session, preserving each chain by keeping a checkpoint.
//!
//! Migration runs idempotently on every [`AuditLog::open`]: if a
//! pre-slice-2 `audit_events` table exists, its rows are copied into
//! `session_events` under the `__pre_migration__` sentinel session
//! and the table is replaced by the view.
//!
//! [`AuditWriter`]: crate::AuditWriter
//! [`SessionEvent::AuditLegacy`]: crate::SessionEvent::AuditLegacy

use std::path::Path;
use std::sync::Arc;

use chrono::{DateTime, Utc};

use crate::error::AuditError;
use crate::event::{AuditEvent, StoredEvent};
use crate::legacy_compat;
use crate::session_log::{SchemaDriftRecord, SessionId, SqliteSessionLog};
use crate::signing::AuditSigningKey;

/// Query parameters for filtering audit events.
#[derive(Debug, Default)]
pub struct AuditQuery {
    pub action: Option<String>,
    pub channel: Option<String>,
    pub actor: Option<String>,
    pub session: Option<String>,
    pub since: Option<DateTime<Utc>>,
    pub until: Option<DateTime<Utc>>,
    pub limit: Option<usize>,
}

/// Result of hash chain verification.
///
/// The chain is per-session (one chain per `session_id`).
/// `Ok::rows_verified` is the total across every session in
/// `session_events`. Signature aggregates count `ChainHead` rows and
/// the distinct keys that signed them across every session.
/// `Broken`, `SignatureInvalid`, and `MissingChainHead` are reported
/// on the first session that fails the relevant check; the verifier
/// short-circuits on the first failure.
#[derive(Debug)]
pub enum VerifyResult {
    /// All per-session chains are intact and every observed
    /// signature verified. Signature counters are zero on
    /// transition-era logs that have no `ChainHead` rows.
    Ok {
        /// Total number of session-event rows verified across all
        /// sessions.
        rows_verified: usize,
        /// Number of sessions inspected.
        sessions_total: usize,
        /// Number of `ChainHead` rows whose signature verified.
        signed_heads_count: usize,
        /// Number of `ChainHead` rows that did not carry a
        /// signature. Always zero under the current schema since
        /// every `ChainHead` is signed by construction. Reserved
        /// for forward-compat with a future variant.
        unsigned_heads_count: usize,
        /// Number of `ChainHead` rows whose signature did not
        /// verify. Always zero in this variant: an invalid
        /// signature is a hard fail and surfaces as
        /// `SignatureInvalid`.
        invalid_signatures_count: usize,
        /// Number of sessions that contained zero `ChainHead`
        /// rows. Non-zero values indicate transition-era sessions
        /// that were appended before the gateway started signing
        /// chain heads. With `--require-signed` set, the verifier
        /// hard-fails on this condition rather than reporting it
        /// here.
        sessions_with_no_signed_heads: usize,
        /// Distinct hex-encoded `signing_key_id` values observed,
        /// sorted ascending. Length > 1 indicates a key rotation
        /// across the verified window.
        signing_key_ids_seen: Vec<String>,
        /// Largest `unsigned_tail_len` observed across sessions.
        /// Operators can use this to surface stale tails without
        /// failing the verify outright.
        unsigned_tail_max_len: usize,
        /// Rows whose payload could not be deserialized into a
        /// [`crate::SessionEvent`]. The chain hash is computed over
        /// raw payload bytes and verifies independently, so these
        /// rows do not break chain integrity; they are surfaced
        /// here so the writer's continuous-verify loop can warn,
        /// emit `audit.row_unverifiable` events, and continue.
        /// Empty in the common case.
        schema_drift_records: Vec<SchemaDriftRecord>,
    },
    /// At least one per-session chain is broken.
    Broken {
        /// Session that failed verification.
        session_id: SessionId,
        /// Per-session sequence number where the break was
        /// detected. The events with smaller `seq` in this session
        /// did verify; events at and after this one cannot be
        /// relied on.
        seq: u64,
        /// Hash value that the verifier computed or expected.
        expected_hash: String,
        /// Hash value that was actually present in the row.
        actual_hash: String,
        /// Number of events that verified before the break,
        /// summed across all sessions plus the per-session count
        /// up to (but not including) the breaking event.
        verified_count: u64,
    },
    /// A `ChainHead` row's Ed25519 signature did not verify, or its
    /// claimed `prev_chain_hash` / `current_chain_hash` did not
    /// match the stored chain hashes at the cited sequence numbers.
    /// Always a hard fail. The chain check completed successfully
    /// for this session before the signature pass uncovered the
    /// break.
    SignatureInvalid {
        session_id: SessionId,
        /// `seq` of the offending `ChainHead` row.
        seq: u64,
        /// Hex-encoded `signing_key_id` from the row.
        signing_key_id: String,
        /// Verdict reason: which specific check failed.
        reason: String,
        /// Number of events that verified across all sessions
        /// before the signature break.
        verified_count: u64,
        /// Count of invalid signatures observed before bailing.
        /// Always 1 in this variant since the verifier
        /// short-circuits on the first invalid signature; the
        /// field is here so JSON consumers see the counter on
        /// every result kind.
        invalid_signatures_count: usize,
    },
    /// The session has no `ChainHead` rows and `--require-signed`
    /// is set. The chain check itself passed.
    MissingChainHead {
        session_id: SessionId,
        /// Number of rows in the session.
        rows: u64,
        /// Number of events that verified across all sessions
        /// before the missing-head break.
        verified_count: u64,
    },
    /// No session events present.
    Empty,
}

/// Façade over [`SqliteSessionLog`] that preserves the legacy
/// [`AuditLog`] API.
pub struct AuditLog {
    inner: Arc<SqliteSessionLog>,
}

impl AuditLog {
    /// Open or create an audit log at `db_path`. Runs the
    /// idempotent migration from the pre-slice-2 `audit_events`
    /// table layout to the new `session_events` table + view.
    pub fn open(db_path: &Path) -> Result<Self, AuditError> {
        let inner = SqliteSessionLog::open(db_path)?;
        legacy_compat::migrate_legacy_audit_events(&inner)?;
        Ok(Self {
            inner: Arc::new(inner),
        })
    }

    /// Open or create an audit log at `db_path` with an audit
    /// signing key. ChainHead records are emitted on the cadence
    /// defined in [`crate::session_log`].
    pub fn open_with_signer(
        db_path: &Path,
        signer: Arc<AuditSigningKey>,
    ) -> Result<Self, AuditError> {
        let inner = SqliteSessionLog::open_with_signer(db_path, signer)?;
        legacy_compat::migrate_legacy_audit_events(&inner)?;
        Ok(Self {
            inner: Arc::new(inner),
        })
    }

    /// Open an in-memory audit log (test helper). The view is
    /// created on the fresh in-memory database; no legacy table
    /// exists, so migration is a no-op.
    pub fn open_in_memory() -> Result<Self, AuditError> {
        let inner = SqliteSessionLog::open_in_memory()?;
        legacy_compat::migrate_legacy_audit_events(&inner)?;
        Ok(Self {
            inner: Arc::new(inner),
        })
    }

    /// Borrow the inner session log. Used by the [`AuditWriter`]
    /// flush task to share a single log instance across the writer
    /// and any future direct callers.
    ///
    /// [`AuditWriter`]: crate::AuditWriter
    pub fn session_log(&self) -> Arc<SqliteSessionLog> {
        self.inner.clone()
    }

    /// Write a batch of [`AuditEvent`]s. Each event is converted
    /// into a [`crate::SessionEvent::AuditLegacy`] and appended to
    /// the session log under the event's `session` field, or under
    /// the system sentinel session if `session` is empty.
    pub fn write_batch(&self, events: &[AuditEvent]) -> Result<(), AuditError> {
        for event in events {
            legacy_compat::write_legacy(&self.inner, event)?;
        }
        Ok(())
    }

    /// Query audit events through the legacy `audit_events` view.
    pub fn query(&self, q: &AuditQuery) -> Result<Vec<StoredEvent>, AuditError> {
        legacy_compat::query_legacy(&self.inner, q)
    }

    /// Return every distinct session id present in `session_events`.
    /// Used by the attestation-verification routines to walk all
    /// sessions without re-implementing the SQL.
    pub fn list_session_ids(&self) -> Result<Vec<String>, AuditError> {
        legacy_compat::list_session_ids(&self.inner)
    }

    /// Verify every per-session chain in `session_events`.
    /// Accepts unsigned (transition-era) sessions; missing
    /// `ChainHead` rows are reported in the `Ok` variant rather
    /// than failed.
    pub fn verify(&self) -> Result<VerifyResult, AuditError> {
        legacy_compat::verify_legacy(&self.inner, false)
    }

    /// Verify every per-session chain in `session_events` with
    /// `--require-signed` semantics. Sessions that have zero
    /// `ChainHead` rows hard-fail with `MissingChainHead`. All
    /// other behaviour matches [`AuditLog::verify`].
    pub fn verify_require_signed(&self) -> Result<VerifyResult, AuditError> {
        legacy_compat::verify_legacy(&self.inner, true)
    }

    /// Prune events older than `retention_days`. Per-session chains
    /// are preserved by keeping the most recent event before the
    /// cutoff in each session as a checkpoint.
    pub fn prune(&self, retention_days: u32) -> Result<usize, AuditError> {
        legacy_compat::prune_legacy(&self.inner, retention_days)
    }
}
