use db::sqlx;
use model::notify::UserAppNotifyModel;
use model::users::UserNotificationModel;
use serde::{Deserialize, Serialize};
use session::Session;

use crate::{AppService, Pagination, error::AppError, session_user};

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct UserNotificationConfig {
    pub email_enabled: bool,
    pub in_app_enabled: bool,
    pub push_enabled: bool,
    pub digest_mode: String,
    pub dnd_enabled: bool,
    pub dnd_start_minute: Option<i32>,
    pub dnd_end_minute: Option<i32>,
    pub marketing_enabled: bool,
    pub security_enabled: bool,
    pub product_enabled: bool,
    pub push_subscription_endpoint: Option<String>,
    pub push_subscription_keys_p256dh: Option<String>,
    pub push_subscription_keys_auth: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AppNotificationItem {
    pub id: uuid::Uuid,
    pub title: String,
    pub body: String,
    pub notify_type: String,
    #[schema(value_type = Option<String>)]
    pub read_at: Option<chrono::DateTime<chrono::Utc>>,
    #[schema(value_type = String)]
    pub created_at: chrono::DateTime<chrono::Utc>,
}

impl From<UserAppNotifyModel> for AppNotificationItem {
    fn from(n: UserAppNotifyModel) -> Self {
        Self {
            id: n.id,
            title: n.title,
            body: n.body,
            notify_type: n.notify_type,
            read_at: n.read_at,
            created_at: n.created_at,
        }
    }
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct UpdateUserNotificationConfig {
    pub email_enabled: Option<bool>,
    pub in_app_enabled: Option<bool>,
    pub push_enabled: Option<bool>,
    pub digest_mode: Option<String>,
    pub dnd_enabled: Option<bool>,
    pub dnd_start_minute: Option<Option<i32>>,
    pub dnd_end_minute: Option<Option<i32>>,
    pub marketing_enabled: Option<bool>,
    pub security_enabled: Option<bool>,
    pub product_enabled: Option<bool>,
    pub push_subscription_endpoint: Option<Option<String>>,
    pub push_subscription_keys_p256dh: Option<Option<String>>,
    pub push_subscription_keys_auth: Option<Option<String>>,
}

impl AppService {
    pub async fn user_update_notification_config(
        &self,
        ctx: &Session,
        params: UpdateUserNotificationConfig,
    ) -> Result<UserNotificationConfig, AppError> {
        let user_uid = session_user(ctx)?;
        let mut config = self.user_notification_config(user_uid).await?;
        if let Some(email_enabled) = params.email_enabled {
            config.email_enabled = email_enabled;
        }
        if let Some(in_app_enabled) = params.in_app_enabled {
            config.in_app_enabled = in_app_enabled;
        }
        if let Some(push_enabled) = params.push_enabled {
            config.push_enabled = push_enabled;
        }
        if let Some(digest_mode) = params.digest_mode {
            config.digest_mode = digest_mode;
        }
        if let Some(dnd_enabled) = params.dnd_enabled {
            config.dnd_enabled = dnd_enabled;
        }
        if let Some(dnd_start_minute) = params.dnd_start_minute {
            config.dnd_start_minute = dnd_start_minute;
        }
        if let Some(dnd_end_minute) = params.dnd_end_minute {
            config.dnd_end_minute = dnd_end_minute;
        }
        if let Some(marketing_enabled) = params.marketing_enabled {
            config.marketing_enabled = marketing_enabled;
        }
        if let Some(security_enabled) = params.security_enabled {
            config.security_enabled = security_enabled;
        }
        if let Some(product_enabled) = params.product_enabled {
            config.product_enabled = product_enabled;
        }
        if let Some(push_subscription_endpoint) =
            params.push_subscription_endpoint
        {
            config.push_subscription_endpoint = push_subscription_endpoint;
        }
        if let Some(push_subscription_keys_p256dh) =
            params.push_subscription_keys_p256dh
        {
            config.push_subscription_keys_p256dh =
                push_subscription_keys_p256dh;
        }
        if let Some(push_subscription_keys_auth) =
            params.push_subscription_keys_auth
        {
            config.push_subscription_keys_auth = push_subscription_keys_auth;
        }
        let now = chrono::Utc::now();
        sqlx::query(
            "INSERT INTO user_notification \
                (\"user\", email_enabled, in_app_enabled, push_enabled, digest_mode, dnd_enabled, dnd_start_minute, dnd_end_minute, \
                 marketing_enabled, security_enabled, product_enabled, push_subscription_endpoint, push_subscription_keys_p256dh, \
                 push_subscription_keys_auth, created_at, updated_at) \
             VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $15) \
             ON CONFLICT (\"user\") DO UPDATE SET \
                email_enabled = EXCLUDED.email_enabled, in_app_enabled = EXCLUDED.in_app_enabled, push_enabled = EXCLUDED.push_enabled, \
                digest_mode = EXCLUDED.digest_mode, dnd_enabled = EXCLUDED.dnd_enabled, dnd_start_minute = EXCLUDED.dnd_start_minute, \
                dnd_end_minute = EXCLUDED.dnd_end_minute, marketing_enabled = EXCLUDED.marketing_enabled, security_enabled = EXCLUDED.security_enabled, \
                product_enabled = EXCLUDED.product_enabled, push_subscription_endpoint = EXCLUDED.push_subscription_endpoint, \
                push_subscription_keys_p256dh = EXCLUDED.push_subscription_keys_p256dh, push_subscription_keys_auth = EXCLUDED.push_subscription_keys_auth, \
                updated_at = EXCLUDED.updated_at",
        )
        .bind(user_uid)
        .bind(config.email_enabled)
        .bind(config.in_app_enabled)
        .bind(config.push_enabled)
        .bind(&config.digest_mode)
        .bind(config.dnd_enabled)
        .bind(config.dnd_start_minute)
        .bind(config.dnd_end_minute)
        .bind(config.marketing_enabled)
        .bind(config.security_enabled)
        .bind(config.product_enabled)
        .bind(&config.push_subscription_endpoint)
        .bind(&config.push_subscription_keys_p256dh)
        .bind(&config.push_subscription_keys_auth)
        .bind(now)
        .execute(self.db.writer())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;
        Ok(config)
    }

    pub async fn list_notifications(
        &self,
        ctx: &Session,
        pagination: Pagination,
    ) -> Result<Vec<AppNotificationItem>, AppError> {
        let user_uid = session_user(ctx)?;
        let rows = sqlx::query_as::<_, UserAppNotifyModel>(
            "SELECT id, \"user\", title, body, notify_type, target_type, target_id, metadata, read_at, archived_at, created_at, updated_at \
             FROM user_app_notify WHERE \"user\" = $1 AND archived_at IS NULL \
             ORDER BY created_at DESC \
             OFFSET $2 LIMIT $3",
        )
        .bind(user_uid)
        .bind(pagination.offset() as i64)
        .bind(pagination.limit() as i64)
        .fetch_all(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(rows.into_iter().map(Into::into).collect())
    }

    pub async fn unread_notifications_count(
        &self,
        user_uid: uuid::Uuid,
    ) -> Result<i64, AppError> {
        let row: (Option<i64>,) = sqlx::query_as(
            "SELECT COUNT(*) FROM user_app_notify \
             WHERE \"user\" = $1 AND read_at IS NULL AND archived_at IS NULL",
        )
        .bind(user_uid)
        .fetch_one(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;

        Ok(row.0.unwrap_or(0))
    }

    pub async fn user_notification_config(
        &self,
        user_uid: uuid::Uuid,
    ) -> Result<UserNotificationConfig, AppError> {
        let row = sqlx::query_as::<_, UserNotificationModel>(
            "SELECT \"user\", email_enabled, in_app_enabled, push_enabled, digest_mode, dnd_enabled, dnd_start_minute, dnd_end_minute, \
                    marketing_enabled, security_enabled, product_enabled, push_subscription_endpoint, push_subscription_keys_p256dh, \
                    push_subscription_keys_auth, created_at, updated_at \
             FROM user_notification WHERE \"user\" = $1",
        )
        .bind(user_uid)
        .fetch_optional(self.db.reader())
        .await
        .map_err(|e| AppError::DatabaseError(e.to_string()))?;
        Ok(row.map(Into::into).unwrap_or_default())
    }
}

impl Default for UserNotificationConfig {
    fn default() -> Self {
        Self {
            email_enabled: true,
            in_app_enabled: true,
            push_enabled: false,
            digest_mode: "daily".to_string(),
            dnd_enabled: false,
            dnd_start_minute: None,
            dnd_end_minute: None,
            marketing_enabled: false,
            security_enabled: true,
            product_enabled: true,
            push_subscription_endpoint: None,
            push_subscription_keys_p256dh: None,
            push_subscription_keys_auth: None,
        }
    }
}

impl From<UserNotificationModel> for UserNotificationConfig {
    fn from(value: UserNotificationModel) -> Self {
        Self {
            email_enabled: value.email_enabled,
            in_app_enabled: value.in_app_enabled,
            push_enabled: value.push_enabled,
            digest_mode: value.digest_mode,
            dnd_enabled: value.dnd_enabled,
            dnd_start_minute: value.dnd_start_minute,
            dnd_end_minute: value.dnd_end_minute,
            marketing_enabled: value.marketing_enabled,
            security_enabled: value.security_enabled,
            product_enabled: value.product_enabled,
            push_subscription_endpoint: value.push_subscription_endpoint,
            push_subscription_keys_p256dh: value.push_subscription_keys_p256dh,
            push_subscription_keys_auth: value.push_subscription_keys_auth,
        }
    }
}
