//! Notification Delivery Service
//!
//! Handles the actual delivery of notifications through various channels
//! (IN_APP, EMAIL, SMS). Each channel has its own delivery logic.

use anyhow::{anyhow, Result};
use chrono::Utc;
use sqlx::PgPool;
use uuid::Uuid;

use crate::models::{DeliveryStatus, Notification, NotificationChannel, NotificationDelivery, NotificationStatus};
use crate::services::gmail::{GmailService, SendEmailRequest};

/// Maximum number of delivery attempts before giving up
pub const MAX_DELIVERY_ATTEMPTS: i32 = 5;

/// Notification delivery service
pub struct NotificationDeliveryService;

impl NotificationDeliveryService {
    /// Deliver a notification through the specified channel
    ///
    /// # Arguments
    /// * `pool` - Database connection pool
    /// * `gmail_service` - Optional Gmail service for email delivery
    /// * `delivery_id` - The delivery record to process
    ///
    /// # Returns
    /// * `Ok(())` - Delivery succeeded
    /// * `Err(...)` - Delivery failed (will be retried if attempts < MAX)
    pub async fn deliver(
        pool: &PgPool,
        gmail_service: Option<&GmailService>,
        delivery_id: Uuid,
    ) -> Result<()> {
        // Fetch delivery record with notification
        let delivery = Self::get_delivery(pool, delivery_id).await?;
        let notification = Self::get_notification(pool, delivery.notification_id).await?;

        // Check if we've exceeded max attempts
        if delivery.attempts >= MAX_DELIVERY_ATTEMPTS {
            Self::mark_failed(pool, delivery_id, "Max delivery attempts exceeded").await?;
            return Err(anyhow!("Max delivery attempts exceeded"));
        }

        // Update attempt counter
        Self::increment_attempts(pool, delivery_id).await?;

        // Deliver based on channel
        let result = match delivery.channel {
            NotificationChannel::InApp => Self::deliver_in_app(pool, &delivery).await,
            NotificationChannel::Email => {
                Self::deliver_email(pool, gmail_service, &delivery, &notification).await
            }
            NotificationChannel::Sms => Self::deliver_sms(pool, &delivery, &notification).await,
        };

        match result {
            Ok(external_id) => {
                Self::mark_sent(pool, delivery_id, external_id.as_deref()).await?;
                // Update notification status if all deliveries are complete
                Self::update_notification_status(pool, notification.base.id).await?;
                Ok(())
            }
            Err(e) => {
                tracing::error!(
                    delivery_id = %delivery_id,
                    channel = ?delivery.channel,
                    error = %e,
                    "Delivery failed"
                );
                Self::mark_sending_failed(pool, delivery_id, &e.to_string()).await?;
                Err(e)
            }
        }
    }

    /// Deliver in-app notification (just mark as sent - it's already in the database)
    async fn deliver_in_app(_pool: &PgPool, delivery: &NotificationDelivery) -> Result<Option<String>> {
        tracing::info!(
            delivery_id = %delivery.base.id,
            "In-app notification delivered"
        );
        Ok(None)
    }

    /// Deliver notification via email
    async fn deliver_email(
        pool: &PgPool,
        gmail_service: Option<&GmailService>,
        delivery: &NotificationDelivery,
        notification: &Notification,
    ) -> Result<Option<String>> {
        let gmail = gmail_service
            .ok_or_else(|| anyhow!("Gmail service not configured"))?;

        // Get recipient email address
        let email = Self::get_recipient_email(pool, &notification.recipient_type, notification.recipient_id).await?;

        let Some(email) = email else {
            return Err(anyhow!("Recipient has no email address"));
        };

        // Send email
        let request = SendEmailRequest {
            to: vec![email],
            cc: None,
            bcc: None,
            subject: notification.subject.clone(),
            body: notification.body.clone(),
            content_type: Some("text/html".to_string()),
            from_name: Some("Nexus Notifications".to_string()),
            attachments: None,
        };

        let response = gmail.send_email(request).await?;

        tracing::info!(
            delivery_id = %delivery.base.id,
            message_id = %response.id,
            "Email notification delivered"
        );

        Ok(Some(response.id))
    }

    /// Deliver notification via SMS (placeholder - not implemented yet)
    async fn deliver_sms(
        _pool: &PgPool,
        delivery: &NotificationDelivery,
        _notification: &Notification,
    ) -> Result<Option<String>> {
        tracing::warn!(
            delivery_id = %delivery.base.id,
            "SMS delivery not implemented"
        );
        Err(anyhow!("SMS delivery not implemented"))
    }

    /// Get recipient's email address
    async fn get_recipient_email(pool: &PgPool, recipient_type: &str, recipient_id: Uuid) -> Result<Option<String>> {
        let email: Option<Option<String>> = match recipient_type {
            "team_profile" => {
                sqlx::query_scalar("SELECT email FROM team_profiles WHERE id = $1")
                    .bind(recipient_id)
                    .fetch_optional(pool)
                    .await?
            }
            "customer_profile" => {
                sqlx::query_scalar("SELECT email FROM customer_profiles WHERE id = $1")
                    .bind(recipient_id)
                    .fetch_optional(pool)
                    .await?
            }
            _ => None,
        };

        Ok(email.flatten())
    }

    /// Fetch delivery record
    async fn get_delivery(pool: &PgPool, delivery_id: Uuid) -> Result<NotificationDelivery> {
        let delivery: NotificationDelivery = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, notification_id, channel, status, attempts,
                   last_attempt_at, sent_at, delivered_at, error_message, external_id, metadata
            FROM notification_deliveries
            WHERE id = $1
            "#,
        )
        .bind(delivery_id)
        .fetch_one(pool)
        .await?;

        Ok(delivery)
    }

    /// Fetch notification record
    async fn get_notification(pool: &PgPool, notification_id: Uuid) -> Result<Notification> {
        let notification: Notification = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, recipient_type, recipient_id, rule_id, event_id,
                   status, subject, body, action_url, read_at, metadata
            FROM notifications
            WHERE id = $1
            "#,
        )
        .bind(notification_id)
        .fetch_one(pool)
        .await?;

        Ok(notification)
    }

    /// Increment attempt counter
    async fn increment_attempts(pool: &PgPool, delivery_id: Uuid) -> Result<()> {
        let now = Utc::now();

        sqlx::query(
            r#"
            UPDATE notification_deliveries
            SET attempts = attempts + 1, last_attempt_at = $1, status = $2, updated_at = $1
            WHERE id = $3
            "#,
        )
        .bind(now)
        .bind(DeliveryStatus::Sending)
        .bind(delivery_id)
        .execute(pool)
        .await?;

        Ok(())
    }

    /// Mark delivery as sent
    async fn mark_sent(pool: &PgPool, delivery_id: Uuid, external_id: Option<&str>) -> Result<()> {
        let now = Utc::now();

        sqlx::query(
            r#"
            UPDATE notification_deliveries
            SET status = $1, sent_at = $2, delivered_at = $2, external_id = $3, updated_at = $2
            WHERE id = $4
            "#,
        )
        .bind(DeliveryStatus::Delivered)
        .bind(now)
        .bind(external_id)
        .bind(delivery_id)
        .execute(pool)
        .await?;

        Ok(())
    }

    /// Mark delivery as failed (permanently)
    async fn mark_failed(pool: &PgPool, delivery_id: Uuid, error: &str) -> Result<()> {
        let now = Utc::now();

        sqlx::query(
            r#"
            UPDATE notification_deliveries
            SET status = $1, error_message = $2, updated_at = $3
            WHERE id = $4
            "#,
        )
        .bind(DeliveryStatus::Failed)
        .bind(error)
        .bind(now)
        .bind(delivery_id)
        .execute(pool)
        .await?;

        Ok(())
    }

    /// Mark delivery as failed (will retry)
    async fn mark_sending_failed(pool: &PgPool, delivery_id: Uuid, error: &str) -> Result<()> {
        let now = Utc::now();

        // Check current attempts
        let attempts: i32 = sqlx::query_scalar("SELECT attempts FROM notification_deliveries WHERE id = $1")
            .bind(delivery_id)
            .fetch_one(pool)
            .await?;

        let status = if attempts >= MAX_DELIVERY_ATTEMPTS {
            DeliveryStatus::Failed
        } else {
            DeliveryStatus::Pending // Will be retried
        };

        sqlx::query(
            r#"
            UPDATE notification_deliveries
            SET status = $1, error_message = $2, updated_at = $3
            WHERE id = $4
            "#,
        )
        .bind(status)
        .bind(error)
        .bind(now)
        .bind(delivery_id)
        .execute(pool)
        .await?;

        Ok(())
    }

    /// Update notification status based on delivery statuses
    async fn update_notification_status(pool: &PgPool, notification_id: Uuid) -> Result<()> {
        // Count delivery statuses
        #[derive(sqlx::FromRow)]
        struct DeliveryCounts {
            total: i64,
            delivered: i64,
            failed: i64,
        }

        let counts: DeliveryCounts = sqlx::query_as(
            r#"
            SELECT
                COUNT(*) as total,
                COUNT(*) FILTER (WHERE status IN ('SENT', 'DELIVERED')) as delivered,
                COUNT(*) FILTER (WHERE status = 'FAILED') as failed
            FROM notification_deliveries
            WHERE notification_id = $1
            "#,
        )
        .bind(notification_id)
        .fetch_one(pool)
        .await?;

        let new_status = if counts.delivered > 0 {
            NotificationStatus::Sent
        } else if counts.failed == counts.total {
            NotificationStatus::Failed
        } else {
            NotificationStatus::Pending
        };

        sqlx::query(
            r#"
            UPDATE notifications
            SET status = $1, updated_at = NOW()
            WHERE id = $2 AND status = 'PENDING'
            "#,
        )
        .bind(new_status)
        .bind(notification_id)
        .execute(pool)
        .await?;

        Ok(())
    }

    /// Get pending deliveries for a channel (for batch processing)
    pub async fn get_pending_deliveries(
        pool: &PgPool,
        channel: NotificationChannel,
        limit: i32,
    ) -> Result<Vec<NotificationDelivery>> {
        let deliveries: Vec<NotificationDelivery> = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, notification_id, channel, status, attempts,
                   last_attempt_at, sent_at, delivered_at, error_message, external_id, metadata
            FROM notification_deliveries
            WHERE channel = $1
              AND status IN ('PENDING', 'QUEUED')
              AND attempts < $2
            ORDER BY created_at ASC
            LIMIT $3
            "#,
        )
        .bind(&channel)
        .bind(MAX_DELIVERY_ATTEMPTS)
        .bind(limit)
        .fetch_all(pool)
        .await?;

        Ok(deliveries)
    }
}
