use std::{collections::HashMap, sync::Arc};

use chrono::{DateTime, Utc};
use tokio::sync::RwLock;
use uuid::Uuid;

#[derive(Clone)]
pub struct AuditStore {
    inner: Arc<RwLock<AuditState>>,
}

#[derive(Default)]
struct AuditState {
    event_types_by_name: HashMap<String, Uuid>,
    events: Vec<AuditEventSnapshot>,
    attributes: Vec<AuditEventAttributeSnapshot>,
}

#[derive(Clone)]
#[allow(dead_code)]
struct AuditEventSnapshot {
    id: Uuid,
    audit_event_type_id: Uuid,
    session_id: Option<Uuid>,
    user_id: Option<Uuid>,
    occurred_at: DateTime<Utc>,
}

#[derive(Clone)]
#[allow(dead_code)]
struct AuditEventAttributeSnapshot {
    id: Uuid,
    audit_event_id: Uuid,
    attribute_key: String,
    attribute_value: String,
}

impl AuditStore {
    pub fn new() -> Self {
        Self {
            inner: Arc::new(RwLock::new(AuditState::default())),
        }
    }

    pub async fn record<I, K, V>(
        &self,
        action: impl Into<String>,
        session_id: Option<Uuid>,
        user_id: Option<Uuid>,
        occurred_at: DateTime<Utc>,
        attributes: I,
    ) where
        I: IntoIterator<Item = (K, V)>,
        K: Into<String>,
        V: Into<String>,
    {
        let mut state = self.inner.write().await;
        let action = action.into();
        let event_type_id = *state
            .event_types_by_name
            .entry(action)
            .or_insert_with(Uuid::new_v4);

        let audit_event_id = Uuid::new_v4();
        state.events.push(AuditEventSnapshot {
            id: audit_event_id,
            audit_event_type_id: event_type_id,
            session_id,
            user_id,
            occurred_at,
        });

        for (key, value) in attributes {
            state.attributes.push(AuditEventAttributeSnapshot {
                id: Uuid::new_v4(),
                audit_event_id,
                attribute_key: key.into(),
                attribute_value: value.into(),
            });
        }
    }

    pub async fn counts(&self) -> (usize, usize) {
        let state = self.inner.read().await;
        (state.events.len(), state.attributes.len())
    }
}
