use std::sync::Arc;

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal;
use tokio::sync::Mutex;
use tracing::warn;

/// The prev_hash value for the genesis (first) entry in the chain.
const GENESIS_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";

/// A single tamper-evident audit entry.
///
/// `block_hash = SHA256(prev_hash | seq | entry_id | timestamp | workflow_id |
/// event_type | actor | details_json)`
///
/// Modifying any field invalidates `block_hash`, and since the next entry
/// embeds this hash as `prev_hash`, the entire suffix of the chain is
/// invalidated — making tampering detectable by `verify_integrity`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
    pub seq: i64,
    pub entry_id: String,
    pub timestamp: DateTime<Utc>,
    pub workflow_id: String,
    pub event_type: String,
    pub actor: String,
    pub details: serde_json::Value,
    pub prev_hash: String,
    pub block_hash: String,
}

/// Report returned by `AuditTrail::verify_integrity`.
#[derive(Debug)]
#[allow(dead_code)]
pub struct IntegrityReport {
    pub valid: bool,
    pub total_entries: usize,
    /// Sequence number of the first entry where the chain is broken, if any.
    pub first_tampered_seq: Option<i64>,
}

/// Compute the Merkle block hash for an entry.
///
/// The canonical input is pipe-delimited so that field boundaries cannot be
/// blurred: `prev_hash|seq|entry_id|timestamp_rfc3339|workflow_id|event_type|
/// actor|details_json`.
#[allow(clippy::too_many_arguments)]
fn compute_block_hash(
    prev_hash: &str,
    seq: i64,
    entry_id: &str,
    timestamp: &DateTime<Utc>,
    workflow_id: &str,
    event_type: &str,
    actor: &str,
    details: &serde_json::Value,
) -> String {
    let canonical = format!(
        "{prev_hash}|{seq}|{entry_id}|{ts}|{workflow_id}|{event_type}|{actor}|{details}",
        ts = timestamp.to_rfc3339(),
    );
    hex::encode(Sha256::digest(canonical.as_bytes()))
}

/// Tamper-evident audit trail backed by SurrealDB.
///
/// Writes are serialized through `write_lock` so that the seq/prev_hash pair
/// is always read and written atomically (no gaps, no forks).
#[allow(dead_code)]
pub struct AuditTrail {
    db: Surreal<Client>,
    write_lock: Arc<Mutex<()>>,
}

#[allow(dead_code)]
impl AuditTrail {
    pub fn new(db: Surreal<Client>) -> Self {
        Self {
            db,
            write_lock: Arc::new(Mutex::new(())),
        }
    }

    /// Append a new event to the chain.
    ///
    /// Computes `block_hash` from the previous entry's hash (or GENESIS_HASH
    /// for the first entry) and persists the record to SurrealDB.
    pub async fn log_event(
        &self,
        workflow_id: String,
        event_type: String,
        actor: String,
        details: serde_json::Value,
    ) -> Result<AuditEntry> {
        let _guard = self.write_lock.lock().await;

        let mut response = self
            .db
            .query("SELECT seq, block_hash FROM audit_entries ORDER BY seq DESC LIMIT 1")
            .await
            .context("querying last audit entry")?;

        let last: Vec<serde_json::Value> = response.take(0).context("taking audit query result")?;

        let (next_seq, prev_hash) = match last.first() {
            Some(v) => {
                let seq = v["seq"].as_i64().unwrap_or(0);
                let hash = v["block_hash"].as_str().unwrap_or(GENESIS_HASH).to_string();
                (seq + 1, hash)
            }
            None => (0, GENESIS_HASH.to_string()),
        };

        let entry_id = uuid::Uuid::new_v4().to_string();
        let timestamp = Utc::now();
        let block_hash = compute_block_hash(
            &prev_hash,
            next_seq,
            &entry_id,
            &timestamp,
            &workflow_id,
            &event_type,
            &actor,
            &details,
        );

        let entry = AuditEntry {
            seq: next_seq,
            entry_id,
            timestamp,
            workflow_id,
            event_type,
            actor,
            details,
            prev_hash,
            block_hash,
        };

        let json = serde_json::to_value(&entry).context("serializing audit entry")?;
        let _: Option<serde_json::Value> = self
            .db
            .create("audit_entries")
            .content(json)
            .await
            .context("persisting audit entry to SurrealDB")?;

        Ok(entry)
    }

    /// Fetch all entries for a workflow, ordered by seq ascending.
    pub async fn get_workflow_audit(&self, workflow_id: &str) -> Result<Vec<AuditEntry>> {
        let mut response = self
            .db
            .query("SELECT * FROM audit_entries WHERE workflow_id = $wf ORDER BY seq ASC")
            .bind(("wf", workflow_id.to_string()))
            .await
            .context("querying workflow audit entries")?;

        let raw: Vec<serde_json::Value> = response.take(0).context("taking audit query")?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// Fetch all entries across all workflows, ordered by seq ascending.
    pub async fn get_all_entries(&self) -> Result<Vec<AuditEntry>> {
        let mut response = self
            .db
            .query("SELECT * FROM audit_entries ORDER BY seq ASC")
            .await
            .context("querying all audit entries")?;

        let raw: Vec<serde_json::Value> = response.take(0).context("taking audit query")?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// Fetch entries by event type, ordered by seq ascending.
    pub async fn get_by_event_type(&self, event_type: &str) -> Result<Vec<AuditEntry>> {
        let mut response = self
            .db
            .query("SELECT * FROM audit_entries WHERE event_type = $et ORDER BY seq ASC")
            .bind(("et", event_type.to_string()))
            .await
            .context("querying audit entries by event type")?;

        let raw: Vec<serde_json::Value> = response.take(0).context("taking audit query")?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// Fetch entries by actor, ordered by seq ascending.
    pub async fn get_by_actor(&self, actor: &str) -> Result<Vec<AuditEntry>> {
        let mut response = self
            .db
            .query("SELECT * FROM audit_entries WHERE actor = $actor ORDER BY seq ASC")
            .bind(("actor", actor.to_string()))
            .await
            .context("querying audit entries by actor")?;

        let raw: Vec<serde_json::Value> = response.take(0).context("taking audit query")?;
        Ok(raw
            .into_iter()
            .filter_map(|v| serde_json::from_value(v).ok())
            .collect())
    }

    /// Walk the chain from genesis, recomputing each block hash.
    ///
    /// Returns `valid = false` and `first_tampered_seq` if any entry's stored
    /// hash does not match the recomputed hash, or if `prev_hash` does not
    /// match the previous entry's `block_hash`.
    pub async fn verify_integrity(&self) -> Result<IntegrityReport> {
        let entries = self.get_all_entries().await?;
        let total_entries = entries.len();
        let mut expected_prev = GENESIS_HASH.to_string();
        let mut first_tampered_seq: Option<i64> = None;

        for entry in &entries {
            if entry.prev_hash != expected_prev {
                warn!(
                    seq = entry.seq,
                    stored_prev = %entry.prev_hash,
                    expected_prev = %expected_prev,
                    "Audit chain break: prev_hash mismatch"
                );
                first_tampered_seq = Some(entry.seq);
                break;
            }

            let expected_hash = compute_block_hash(
                &entry.prev_hash,
                entry.seq,
                &entry.entry_id,
                &entry.timestamp,
                &entry.workflow_id,
                &entry.event_type,
                &entry.actor,
                &entry.details,
            );

            if expected_hash != entry.block_hash {
                warn!(
                    seq = entry.seq,
                    stored_hash = %entry.block_hash,
                    expected_hash = %expected_hash,
                    "Audit chain break: block_hash mismatch"
                );
                first_tampered_seq = Some(entry.seq);
                break;
            }

            expected_prev = entry.block_hash.clone();
        }

        Ok(IntegrityReport {
            valid: first_tampered_seq.is_none(),
            total_entries,
            first_tampered_seq,
        })
    }
}

/// Event type constants for audit trail entries.
#[allow(dead_code)]
pub mod events {
    pub const WORKFLOW_CREATED: &str = "workflow_created";
    pub const WORKFLOW_STARTED: &str = "workflow_started";
    pub const WORKFLOW_COMPLETED: &str = "workflow_completed";
    pub const WORKFLOW_FAILED: &str = "workflow_failed";
    pub const WORKFLOW_ROLLED_BACK: &str = "workflow_rolled_back";
    pub const PHASE_STARTED: &str = "phase_started";
    pub const PHASE_COMPLETED: &str = "phase_completed";
    pub const STEP_STARTED: &str = "step_started";
    pub const STEP_COMPLETED: &str = "step_completed";
    pub const STEP_FAILED: &str = "step_failed";
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_details() -> serde_json::Value {
        serde_json::json!({"key": "value", "count": 42})
    }

    fn fixed_ts() -> DateTime<Utc> {
        "2026-02-26T12:00:00Z"
            .parse::<DateTime<Utc>>()
            .expect("valid timestamp")
    }

    #[test]
    fn compute_block_hash_is_deterministic() {
        let ts = fixed_ts();
        let details = make_details();
        let h1 = compute_block_hash(
            GENESIS_HASH,
            0,
            "entry-id-1",
            &ts,
            "wf-1",
            events::WORKFLOW_CREATED,
            "system",
            &details,
        );
        let h2 = compute_block_hash(
            GENESIS_HASH,
            0,
            "entry-id-1",
            &ts,
            "wf-1",
            events::WORKFLOW_CREATED,
            "system",
            &details,
        );
        assert_eq!(h1, h2);
        assert_eq!(h1.len(), 64, "SHA256 hex is always 64 chars");
    }

    #[test]
    fn compute_block_hash_differs_on_prev_hash_change() {
        let ts = fixed_ts();
        let details = make_details();
        let h1 = compute_block_hash(
            GENESIS_HASH,
            0,
            "id",
            &ts,
            "wf-1",
            "created",
            "system",
            &details,
        );
        let h2 = compute_block_hash(
            "deadbeef00000000000000000000000000000000000000000000000000000000",
            0,
            "id",
            &ts,
            "wf-1",
            "created",
            "system",
            &details,
        );
        assert_ne!(h1, h2);
    }

    #[test]
    fn compute_block_hash_differs_on_seq_change() {
        let ts = fixed_ts();
        let details = make_details();
        let h1 = compute_block_hash(GENESIS_HASH, 0, "id", &ts, "wf", "created", "a", &details);
        let h2 = compute_block_hash(GENESIS_HASH, 1, "id", &ts, "wf", "created", "a", &details);
        assert_ne!(h1, h2);
    }

    #[test]
    fn compute_block_hash_differs_on_field_change() {
        let ts = fixed_ts();
        let details = make_details();
        let base = compute_block_hash(
            GENESIS_HASH,
            0,
            "id",
            &ts,
            "wf",
            "created",
            "alice",
            &details,
        );

        let changed_actor =
            compute_block_hash(GENESIS_HASH, 0, "id", &ts, "wf", "created", "bob", &details);
        assert_ne!(base, changed_actor);

        let changed_wf = compute_block_hash(
            GENESIS_HASH,
            0,
            "id",
            &ts,
            "wf-other",
            "created",
            "alice",
            &details,
        );
        assert_ne!(base, changed_wf);

        let changed_details = compute_block_hash(
            GENESIS_HASH,
            0,
            "id",
            &ts,
            "wf",
            "created",
            "alice",
            &serde_json::json!({}),
        );
        assert_ne!(base, changed_details);
    }

    // Integration tests below require a running SurrealDB instance.
    // Run with: cargo test -p vapora-backend -- --ignored

    #[tokio::test]
    #[ignore = "requires SurrealDB at ws://localhost:8000"]
    async fn log_event_persists_and_chains() {
        let db =
            surrealdb::Surreal::new::<surrealdb::engine::remote::ws::Ws>("ws://localhost:8000")
                .await
                .expect("connect to SurrealDB");
        db.signin(surrealdb::opt::auth::Root {
            username: "root".to_string(),
            password: "root".to_string(),
        })
        .await
        .expect("signin");
        db.use_ns("vapora_test")
            .use_db("audit_test")
            .await
            .expect("use ns/db");

        let trail = AuditTrail::new(db);

        let e1 = trail
            .log_event(
                "wf-1".into(),
                events::WORKFLOW_CREATED.into(),
                "system".into(),
                serde_json::json!({"title": "Test"}),
            )
            .await
            .expect("log_event e1");

        let e2 = trail
            .log_event(
                "wf-1".into(),
                events::WORKFLOW_STARTED.into(),
                "system".into(),
                serde_json::json!({}),
            )
            .await
            .expect("log_event e2");

        assert_eq!(e1.seq, 0);
        assert_eq!(e1.prev_hash, GENESIS_HASH);
        assert_eq!(e2.seq, 1);
        assert_eq!(e2.prev_hash, e1.block_hash);
    }

    #[tokio::test]
    #[ignore = "requires SurrealDB at ws://localhost:8000"]
    async fn verify_integrity_valid_chain() {
        let db =
            surrealdb::Surreal::new::<surrealdb::engine::remote::ws::Ws>("ws://localhost:8000")
                .await
                .expect("connect");
        db.signin(surrealdb::opt::auth::Root {
            username: "root".to_string(),
            password: "root".to_string(),
        })
        .await
        .expect("signin");
        db.use_ns("vapora_test")
            .use_db("audit_verify_test")
            .await
            .expect("use ns/db");

        let trail = AuditTrail::new(db);
        trail
            .log_event(
                "wf".into(),
                "created".into(),
                "alice".into(),
                serde_json::json!({}),
            )
            .await
            .expect("e1");
        trail
            .log_event(
                "wf".into(),
                "started".into(),
                "alice".into(),
                serde_json::json!({}),
            )
            .await
            .expect("e2");

        let report = trail.verify_integrity().await.expect("verify");
        assert!(report.valid);
        assert_eq!(report.total_entries, 2);
        assert!(report.first_tampered_seq.is_none());
    }

    #[tokio::test]
    #[ignore = "requires SurrealDB at ws://localhost:8000"]
    async fn get_workflow_audit_filters_by_workflow() {
        let db =
            surrealdb::Surreal::new::<surrealdb::engine::remote::ws::Ws>("ws://localhost:8000")
                .await
                .expect("connect");
        db.signin(surrealdb::opt::auth::Root {
            username: "root".to_string(),
            password: "root".to_string(),
        })
        .await
        .expect("signin");
        db.use_ns("vapora_test")
            .use_db("audit_filter_test")
            .await
            .expect("use ns/db");

        let trail = AuditTrail::new(db);
        trail
            .log_event(
                "wf-A".into(),
                "created".into(),
                "system".into(),
                serde_json::json!({}),
            )
            .await
            .expect("wf-A");
        trail
            .log_event(
                "wf-B".into(),
                "created".into(),
                "system".into(),
                serde_json::json!({}),
            )
            .await
            .expect("wf-B");

        let entries = trail.get_workflow_audit("wf-A").await.expect("query");
        assert_eq!(entries.len(), 1);
        assert_eq!(entries[0].workflow_id, "wf-A");
    }
}
