//! Notification GraphQL types
//!
//! Types for notification rules, notifications, and deliveries.

use async_graphql::{ComplexObject, Context, Enum, Result, SimpleObject};
use chrono::{DateTime, Utc};
use serde_json::Value as JsonValue;
use uuid::Uuid;

use crate::db::Database;
use crate::models::{
    DeliveryStatus, Event, Notification, NotificationChannel, NotificationDelivery,
    NotificationRule, NotificationStatus, TeamProfile, CustomerProfile,
};

use super::{TeamProfileType, CustomerProfileType, ProfileUnion, EventType_};

// ==================== ENUMS ====================

/// Notification channel enum for GraphQL
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum NotificationChannelEnum {
    InApp,
    Email,
    Sms,
}

impl From<NotificationChannel> for NotificationChannelEnum {
    fn from(nc: NotificationChannel) -> Self {
        match nc {
            NotificationChannel::InApp => Self::InApp,
            NotificationChannel::Email => Self::Email,
            NotificationChannel::Sms => Self::Sms,
        }
    }
}

/// Notification status enum for GraphQL
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum NotificationStatusEnum {
    Pending,
    Sent,
    Read,
    Failed,
}

impl From<NotificationStatus> for NotificationStatusEnum {
    fn from(ns: NotificationStatus) -> Self {
        match ns {
            NotificationStatus::Pending => Self::Pending,
            NotificationStatus::Sent => Self::Sent,
            NotificationStatus::Read => Self::Read,
            NotificationStatus::Failed => Self::Failed,
        }
    }
}

/// Delivery status enum for GraphQL
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum DeliveryStatusEnum {
    Pending,
    Queued,
    Sending,
    Sent,
    Delivered,
    Failed,
    Bounced,
}

impl From<DeliveryStatus> for DeliveryStatusEnum {
    fn from(ds: DeliveryStatus) -> Self {
        match ds {
            DeliveryStatus::Pending => Self::Pending,
            DeliveryStatus::Queued => Self::Queued,
            DeliveryStatus::Sending => Self::Sending,
            DeliveryStatus::Sent => Self::Sent,
            DeliveryStatus::Delivered => Self::Delivered,
            DeliveryStatus::Failed => Self::Failed,
            DeliveryStatus::Bounced => Self::Bounced,
        }
    }
}

// ==================== NOTIFICATION RULE ====================

/// Admin-defined rule for triggering notifications
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct NotificationRuleType {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub is_active: bool,
    /// Event types that trigger this rule (array of event type strings)
    pub event_types: JsonValue,
    /// Channels to deliver through (array of channel strings)
    pub channels: JsonValue,
    /// Target roles (array of role strings)
    pub target_roles: Option<JsonValue>,
    /// Custom conditions for triggering
    pub conditions: Option<JsonValue>,
    /// Notification subject template
    pub subject_template: Option<String>,
    /// Notification body template
    pub body_template: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl NotificationRuleType {
    /// Team profiles specifically targeted by this rule
    async fn target_team_profiles(&self, ctx: &Context<'_>) -> Result<Vec<TeamProfileType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let profiles: Vec<TeamProfile> = sqlx::query_as(
            r#"
            SELECT tp.id, tp.created_at, tp.updated_at, tp.first_name, tp.last_name,
                   tp.phone, tp.email, tp.role, tp.status, tp.notes
            FROM team_profiles tp
            INNER JOIN notification_rule_team_profiles nrtp ON tp.id = nrtp.team_profile_id
            WHERE nrtp.rule_id = $1
            ORDER BY tp.last_name, tp.first_name
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(profiles.into_iter().map(TeamProfileType::from).collect())
    }

    /// Customer profiles specifically targeted by this rule
    async fn target_customer_profiles(&self, ctx: &Context<'_>) -> Result<Vec<CustomerProfileType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let profiles: Vec<CustomerProfile> = sqlx::query_as(
            r#"
            SELECT cp.id, cp.created_at, cp.updated_at, cp.customer_id, cp.first_name,
                   cp.last_name, cp.email, cp.phone, cp.status, cp.notes, cp.is_primary
            FROM customer_profiles cp
            INNER JOIN notification_rule_customer_profiles nrcp ON cp.id = nrcp.customer_profile_id
            WHERE nrcp.rule_id = $1
            ORDER BY cp.last_name, cp.first_name
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(profiles.into_iter().map(CustomerProfileType::from).collect())
    }

    /// Notifications generated by this rule
    async fn notifications(
        &self,
        ctx: &Context<'_>,
        #[graphql(default = 50)] limit: i32,
    ) -> Result<Vec<NotificationType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let notifications: Vec<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 rule_id = $1
            ORDER BY created_at DESC
            LIMIT $2
            "#,
        )
        .bind(self.id)
        .bind(limit)
        .fetch_all(&*pool)
        .await?;

        Ok(notifications.into_iter().map(NotificationType::from).collect())
    }
}

impl From<NotificationRule> for NotificationRuleType {
    fn from(r: NotificationRule) -> Self {
        Self {
            id: r.base.id,
            name: r.name,
            description: r.description,
            is_active: r.is_active,
            event_types: r.event_types,
            channels: r.channels,
            target_roles: r.target_roles,
            conditions: r.conditions,
            subject_template: r.subject_template,
            body_template: r.body_template,
            created_at: r.base.created_at,
            updated_at: r.base.updated_at,
        }
    }
}

// ==================== NOTIFICATION ====================

/// Individual notification instance
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct NotificationType {
    pub id: Uuid,
    /// Recipient type ("team_profile" or "customer_profile")
    pub recipient_type: String,
    pub recipient_id: Uuid,
    pub rule_id: Option<Uuid>,
    pub event_id: Option<Uuid>,
    pub status: NotificationStatusEnum,
    pub subject: String,
    pub body: String,
    /// URL to navigate to when notification is clicked
    pub action_url: Option<String>,
    pub read_at: Option<DateTime<Utc>>,
    pub metadata: Option<JsonValue>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl NotificationType {
    /// The recipient's profile (polymorphic)
    async fn recipient_profile(&self, ctx: &Context<'_>) -> Result<Option<ProfileUnion>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        match self.recipient_type.as_str() {
            "team_profile" => {
                let profile: Option<TeamProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
                    FROM team_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.recipient_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::TeamProfile(TeamProfileType::from(p))))
            }
            "customer_profile" => {
                let profile: Option<CustomerProfile> = sqlx::query_as(
                    r#"
                    SELECT id, created_at, updated_at, customer_id, first_name, last_name, email, phone, status, notes, is_primary
                    FROM customer_profiles
                    WHERE id = $1
                    "#,
                )
                .bind(self.recipient_id)
                .fetch_optional(&*pool)
                .await?;

                Ok(profile.map(|p| ProfileUnion::CustomerProfile(CustomerProfileType::from(p))))
            }
            _ => Ok(None),
        }
    }

    /// The rule that triggered this notification
    async fn rule(&self, ctx: &Context<'_>) -> Result<Option<NotificationRuleType>> {
        let Some(rule_id) = self.rule_id else {
            return Ok(None);
        };

        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let rule: Option<NotificationRule> = sqlx::query_as(
            r#"
            SELECT id, created_at, updated_at, name, description, is_active, event_types,
                   channels, target_roles, conditions, subject_template, body_template
            FROM notification_rules
            WHERE id = $1
            "#,
        )
        .bind(rule_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(rule.map(NotificationRuleType::from))
    }

    /// The event that triggered this notification
    async fn event(&self, ctx: &Context<'_>) -> Result<Option<EventType_>> {
        let Some(event_id) = self.event_id else {
            return Ok(None);
        };

        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        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(event_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(event.map(EventType_::from))
    }

    /// Delivery attempts for this notification
    async fn deliveries(&self, ctx: &Context<'_>) -> Result<Vec<NotificationDeliveryType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        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 notification_id = $1
            ORDER BY created_at ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(deliveries.into_iter().map(NotificationDeliveryType::from).collect())
    }

    /// Whether this notification has been read
    async fn is_read(&self) -> bool {
        self.read_at.is_some() || self.status == NotificationStatusEnum::Read
    }
}

impl From<Notification> for NotificationType {
    fn from(n: Notification) -> Self {
        Self {
            id: n.base.id,
            recipient_type: n.recipient_type,
            recipient_id: n.recipient_id,
            rule_id: n.rule_id,
            event_id: n.event_id,
            status: n.status.into(),
            subject: n.subject,
            body: n.body,
            action_url: n.action_url,
            read_at: n.read_at,
            metadata: n.metadata,
            created_at: n.base.created_at,
            updated_at: n.base.updated_at,
        }
    }
}

// ==================== NOTIFICATION DELIVERY ====================

/// Tracks delivery attempts per channel
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct NotificationDeliveryType {
    pub id: Uuid,
    pub notification_id: Uuid,
    pub channel: NotificationChannelEnum,
    pub status: DeliveryStatusEnum,
    pub attempts: i32,
    pub last_attempt_at: Option<DateTime<Utc>>,
    pub sent_at: Option<DateTime<Utc>>,
    pub delivered_at: Option<DateTime<Utc>>,
    pub error_message: Option<String>,
    /// External service ID (e.g., email provider message ID)
    pub external_id: Option<String>,
    pub metadata: Option<JsonValue>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[ComplexObject]
impl NotificationDeliveryType {
    /// The notification this delivery is for
    async fn notification(&self, ctx: &Context<'_>) -> Result<Option<NotificationType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let notification: Option<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(self.notification_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(notification.map(NotificationType::from))
    }

    /// Whether delivery was successful
    async fn is_successful(&self) -> bool {
        matches!(
            self.status,
            DeliveryStatusEnum::Sent | DeliveryStatusEnum::Delivered
        )
    }

    /// Whether delivery has permanently failed
    async fn is_failed(&self) -> bool {
        matches!(
            self.status,
            DeliveryStatusEnum::Failed | DeliveryStatusEnum::Bounced
        )
    }
}

impl From<NotificationDelivery> for NotificationDeliveryType {
    fn from(d: NotificationDelivery) -> Self {
        Self {
            id: d.base.id,
            notification_id: d.notification_id,
            channel: d.channel.into(),
            status: d.status.into(),
            attempts: d.attempts,
            last_attempt_at: d.last_attempt_at,
            sent_at: d.sent_at,
            delivered_at: d.delivered_at,
            error_message: d.error_message,
            external_id: d.external_id,
            metadata: d.metadata,
            created_at: d.base.created_at,
            updated_at: d.base.updated_at,
        }
    }
}
