//! Notification Background Jobs
//!
//! Apalis jobs for processing events and delivering notifications.
//! These jobs are registered with the Apalis worker and executed asynchronously.

use serde::{Deserialize, Serialize};
use std::sync::Arc;
use uuid::Uuid;

use crate::db::Database;
use crate::models::Event;
use crate::services::gmail::GmailService;
use crate::services::job_queue::JobQueue;
use crate::services::{NotificationDeliveryService, NotificationProcessor, MAX_DELIVERY_ATTEMPTS};

// ==================== JOB DEFINITIONS ====================

/// Job to process an event and create notifications
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessEventJob {
    pub event_id: Uuid,
}

impl ProcessEventJob {
    pub fn new(event_id: Uuid) -> Self {
        Self { event_id }
    }
}

/// Job to deliver a notification through a specific channel
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeliverNotificationJob {
    pub delivery_id: Uuid,
    pub attempt: u32,
}

impl DeliverNotificationJob {
    pub fn new(delivery_id: Uuid) -> Self {
        Self {
            delivery_id,
            attempt: 1,
        }
    }

    pub fn with_attempt(delivery_id: Uuid, attempt: u32) -> Self {
        Self {
            delivery_id,
            attempt,
        }
    }
}

// ==================== JOB CONTEXT ====================

/// Shared context for notification jobs
pub struct NotificationJobContext {
    pub db: Database,
    pub gmail_service: Option<Arc<GmailService>>,
    pub job_queue: JobQueue,
}

impl NotificationJobContext {
    pub fn new(db: Database, gmail_service: Option<Arc<GmailService>>, job_queue: JobQueue) -> Self {
        Self {
            db,
            gmail_service,
            job_queue,
        }
    }
}

// ==================== JOB EXECUTION ====================

impl ProcessEventJob {
    /// Execute the job - process an event and create notifications
    pub async fn execute(&self, ctx: &NotificationJobContext) -> anyhow::Result<()> {
        tracing::info!(event_id = %self.event_id, "Processing event for notifications");

        // Get current pool (supports credential refresh)
        let pool = ctx.db.pool().await;

        // Fetch the event
        let event: Option<Event> = sqlx::query_as(
            r#"
            SELECT id, event_type, entity_type, entity_id, actor_type, actor_id,
                   metadata, timestamp, created_at
            FROM events
            WHERE id = $1
            "#,
        )
        .bind(self.event_id)
        .fetch_optional(&*pool)
        .await?;

        let Some(event) = event else {
            tracing::warn!(event_id = %self.event_id, "Event not found, skipping notification processing");
            return Ok(());
        };

        // Process the event and create notifications
        let notifications = NotificationProcessor::process_event(&*pool, &event).await?;

        tracing::info!(
            event_id = %self.event_id,
            notification_count = notifications.len(),
            "Created notifications for event"
        );

        // Queue delivery jobs for each notification's deliveries
        for notification in &notifications {
            let delivery_ids: Vec<Uuid> = sqlx::query_scalar(
                "SELECT id FROM notification_deliveries WHERE notification_id = $1",
            )
            .bind(notification.base.id)
            .fetch_all(&*pool)
            .await?;

            for delivery_id in delivery_ids {
                if let Err(e) = ctx.job_queue.queue_deliver_notification(delivery_id).await {
                    tracing::error!(
                        delivery_id = %delivery_id,
                        error = %e,
                        "Failed to queue notification delivery job"
                    );
                }
            }
        }

        Ok(())
    }
}

impl DeliverNotificationJob {
    /// Execute the job - deliver a notification
    pub async fn execute(&self, ctx: &NotificationJobContext) -> anyhow::Result<()> {
        tracing::info!(
            delivery_id = %self.delivery_id,
            attempt = self.attempt,
            "Delivering notification"
        );

        // Check if we've exceeded max attempts
        if self.attempt > MAX_DELIVERY_ATTEMPTS as u32 {
            tracing::warn!(
                delivery_id = %self.delivery_id,
                "Max delivery attempts exceeded"
            );
            return Ok(()); // Don't retry further
        }

        // Get current pool (supports credential refresh)
        let pool = ctx.db.pool().await;

        // Attempt delivery
        let result = NotificationDeliveryService::deliver(
            &*pool,
            ctx.gmail_service.as_ref().map(|s| s.as_ref()),
            self.delivery_id,
        )
        .await;

        match result {
            Ok(_) => {
                tracing::info!(delivery_id = %self.delivery_id, "Notification delivered successfully");
                Ok(())
            }
            Err(e) => {
                tracing::error!(
                    delivery_id = %self.delivery_id,
                    attempt = self.attempt,
                    error = %e,
                    "Notification delivery failed"
                );

                // Queue retry with incremented attempt
                if self.attempt < MAX_DELIVERY_ATTEMPTS as u32 {
                    if let Err(queue_err) = ctx.job_queue.queue_delivery_retry(self.delivery_id, self.attempt + 1).await {
                        tracing::error!(
                            delivery_id = %self.delivery_id,
                            error = %queue_err,
                            "Failed to queue delivery retry"
                        );
                    }
                }

                // Return Ok so Apalis doesn't automatically retry
                Ok(())
            }
        }
    }
}

// ==================== QUEUE NAMES ====================

/// Queue name for event processing jobs
pub const PROCESS_EVENT_QUEUE: &str = "nexus:notifications:process_event";

/// Queue name for notification delivery jobs
pub const DELIVER_NOTIFICATION_QUEUE: &str = "nexus:notifications:deliver";

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

    #[test]
    fn test_job_serialization() {
        let event_job = ProcessEventJob::new(Uuid::new_v4());
        let json = serde_json::to_string(&event_job).unwrap();
        let _: ProcessEventJob = serde_json::from_str(&json).unwrap();

        let delivery_job = DeliverNotificationJob::new(Uuid::new_v4());
        let json = serde_json::to_string(&delivery_job).unwrap();
        let _: DeliverNotificationJob = serde_json::from_str(&json).unwrap();
    }
}
