//! Event Publisher Service
//!
//! Publishes events to the audit trail with automatic metadata enrichment.
//! Handles the logic for whether events should be created based on their
//! criticality and matching notification rules.

use anyhow::Result;
use serde_json::Value as JsonValue;
use sqlx::PgPool;
use uuid::Uuid;

use crate::models::{Event, EventType};
use crate::services::job_queue::JobQueue;
use crate::services::metadata::MetadataEnricher;

/// Event publisher service
pub struct EventPublisher;

impl EventPublisher {
    /// Publish an event with automatic metadata enrichment
    ///
    /// # Arguments
    /// * `pool` - Database connection pool
    /// * `event_type` - The type of event
    /// * `entity_type` - Entity type string (e.g., "service", "project", "account")
    /// * `entity_id` - UUID of the entity
    /// * `actor` - Optional tuple of (actor_type, actor_id)
    /// * `metadata` - Optional additional metadata (will be merged with enriched data)
    ///
    /// # Returns
    /// * `Ok(Some(Event))` - Event was created
    /// * `Ok(None)` - Event was skipped (non-critical with no matching rules)
    /// * `Err(...)` - Database error
    pub async fn publish(
        pool: &PgPool,
        event_type: EventType,
        entity_type: &str,
        entity_id: Uuid,
        actor: Option<(&str, Uuid)>,
        metadata: Option<JsonValue>,
    ) -> Result<Option<Event>> {
        // Check if this event should be created
        if !Self::is_mission_critical(&event_type) {
            let has_rules = Self::has_matching_rules(pool, &event_type).await?;
            if !has_rules {
                tracing::debug!(
                    event_type = ?event_type,
                    "Skipping non-critical event with no matching notification rules"
                );
                return Ok(None);
            }
        }

        // Enrich metadata with context
        let enriched_metadata =
            MetadataEnricher::enrich(pool, &event_type, entity_type, entity_id, metadata).await;

        // Create the event
        let (actor_type, actor_id) = match actor {
            Some((t, id)) => (Some(t.to_string()), Some(id)),
            None => (None, None),
        };

        let event = Event::new(
            event_type,
            entity_type,
            entity_id,
            actor_type,
            actor_id,
            Some(enriched_metadata),
        );

        // Insert into database
        let created: Event = sqlx::query_as(
            r#"
            INSERT INTO events (id, event_type, entity_type, entity_id, actor_type, actor_id, metadata, timestamp, created_at)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
            RETURNING id, event_type, entity_type, entity_id, actor_type, actor_id, metadata, timestamp, created_at
            "#,
        )
        .bind(event.id)
        .bind(&event.event_type)
        .bind(&event.entity_type)
        .bind(event.entity_id)
        .bind(&event.actor_type)
        .bind(event.actor_id)
        .bind(&event.metadata)
        .bind(event.timestamp)
        .bind(event.created_at)
        .fetch_one(pool)
        .await?;

        tracing::info!(
            event_id = %created.id,
            event_type = ?created.event_type,
            entity_type = %created.entity_type,
            entity_id = %created.entity_id,
            "Event published"
        );

        Ok(Some(created))
    }

    /// Publish an event and queue notification processing job
    ///
    /// Same as `publish()` but also queues a background job to process
    /// the event and create notifications.
    pub async fn publish_and_queue(
        pool: &PgPool,
        job_queue: &JobQueue,
        event_type: EventType,
        entity_type: &str,
        entity_id: Uuid,
        actor: Option<(&str, Uuid)>,
        metadata: Option<JsonValue>,
    ) -> Result<Option<Event>> {
        let event = Self::publish(pool, event_type, entity_type, entity_id, actor, metadata).await?;

        // Queue notification processing if event was created
        if let Some(ref created) = event {
            if let Err(e) = job_queue.queue_process_event(created.id).await {
                tracing::error!(
                    event_id = %created.id,
                    error = %e,
                    "Failed to queue event processing job"
                );
                // Don't fail the publish - event is still created
            }
        }

        Ok(event)
    }

    /// Publish a system event (no actor)
    pub async fn publish_system(
        pool: &PgPool,
        event_type: EventType,
        entity_type: &str,
        entity_id: Uuid,
        metadata: Option<JsonValue>,
    ) -> Result<Option<Event>> {
        Self::publish(
            pool,
            event_type,
            entity_type,
            entity_id,
            Some(("system", Uuid::nil())),
            metadata,
        )
        .await
    }

    /// Check if an event type is mission-critical (always created)
    ///
    /// Mission-critical events are always recorded regardless of whether
    /// there are matching notification rules. These are events that are
    /// important for auditing and compliance.
    pub fn is_mission_critical(event_type: &EventType) -> bool {
        matches!(
            event_type,
            // Session lifecycle - critical for time tracking
            EventType::ServiceSessionStarted
                | EventType::ServiceSessionEnded
                | EventType::ServiceSessionReverted
                | EventType::ProjectSessionStarted
                | EventType::ProjectSessionEnded
                | EventType::ProjectSessionReverted
                // Invoice lifecycle - critical for accounting
                | EventType::InvoiceCreated
                | EventType::InvoiceSent
                | EventType::InvoicePaid
                | EventType::InvoiceOverdue
                // Status changes - critical for workflow
                | EventType::ServiceStatusChanged
                | EventType::ProjectStatusChanged
                | EventType::AccountStatusChanged
                | EventType::CustomerStatusChanged
                // Profile lifecycle - critical for access control
                | EventType::TeamProfileCreated
                | EventType::TeamProfileDeleted
                | EventType::TeamProfileStatusChanged
                | EventType::TeamProfileRoleChanged
                | EventType::CustomerProfileCreated
                | EventType::CustomerProfileDeleted
                | EventType::CustomerProfileStatusChanged
                | EventType::CustomerProfileAccessGranted
                | EventType::CustomerProfileAccessRevoked
                // Report lifecycle - critical for payroll
                | EventType::ReportSubmitted
                | EventType::ReportApproved
                // System events
                | EventType::SystemStartup
                | EventType::SystemShutdown
        )
    }

    /// Check if there are any active notification rules matching this event type
    async fn has_matching_rules(pool: &PgPool, event_type: &EventType) -> Result<bool> {
        // Convert event type to the string format stored in JSONB
        let event_type_str = format!("{:?}", event_type);

        // Check if any active rule's event_types array contains this event type
        let exists: bool = sqlx::query_scalar(
            r#"
            SELECT EXISTS(
                SELECT 1 FROM notification_rules
                WHERE is_active = true
                AND event_types @> $1::jsonb
            )
            "#,
        )
        .bind(serde_json::json!([event_type_str]))
        .fetch_one(pool)
        .await?;

        Ok(exists)
    }
}

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

    #[test]
    fn test_mission_critical_events() {
        // Session events should be mission critical
        assert!(EventPublisher::is_mission_critical(
            &EventType::ServiceSessionStarted
        ));
        assert!(EventPublisher::is_mission_critical(
            &EventType::ServiceSessionEnded
        ));
        assert!(EventPublisher::is_mission_critical(
            &EventType::ProjectSessionStarted
        ));

        // Invoice events should be mission critical
        assert!(EventPublisher::is_mission_critical(&EventType::InvoicePaid));
        assert!(EventPublisher::is_mission_critical(&EventType::InvoiceSent));

        // Profile events should be mission critical
        assert!(EventPublisher::is_mission_critical(
            &EventType::TeamProfileCreated
        ));
        assert!(EventPublisher::is_mission_critical(
            &EventType::CustomerProfileAccessGranted
        ));

        // Regular CRUD events should not be mission critical
        assert!(!EventPublisher::is_mission_critical(
            &EventType::ServiceCreated
        ));
        assert!(!EventPublisher::is_mission_critical(
            &EventType::ProjectUpdated
        ));
        assert!(!EventPublisher::is_mission_critical(
            &EventType::AccountDeleted
        ));
    }
}
