use crate::domain::entities::daily_quota;
use anyhow::Result;
use sea_orm::{
    sea_query::{Expr, OnConflict},
    ColumnTrait, ConnectionTrait, DatabaseConnection, EntityTrait, QueryFilter, Set,
};

/// 每日额度数据访问
pub struct QuotaRepository {
    db: DatabaseConnection,
}

impl QuotaRepository {
    pub fn new(db: DatabaseConnection) -> Self {
        Self { db }
    }

    pub fn today() -> chrono::NaiveDate {
        chrono::Utc::now()
            .with_timezone(&chrono_tz::Asia::Shanghai)
            .date_naive()
    }

    /// 获取用户今日额度（不存在则 None）
    pub async fn get_today(&self, user_id: &str) -> Result<Option<daily_quota::Model>> {
        Ok(daily_quota::Entity::find()
            .filter(daily_quota::Column::UserId.eq(user_id))
            .filter(daily_quota::Column::Date.eq(Self::today()))
            .one(&self.db)
            .await?)
    }

    async fn ensure_today<C>(db: &C, user_id: &str) -> Result<()>
    where
        C: ConnectionTrait,
    {
        let today = Self::today();
        let now = chrono::Utc::now().naive_utc();
        daily_quota::Entity::insert(daily_quota::ActiveModel {
            user_id: Set(user_id.into()),
            date: Set(today),
            mailboxes_created: Set(0),
            emails_received: Set(0),
            bytes_received: Set(0),
            updated_at: Set(now),
        })
        .on_conflict(
            OnConflict::columns([daily_quota::Column::UserId, daily_quota::Column::Date])
                .do_nothing()
                .to_owned(),
        )
        .exec_without_returning(db)
        .await?;
        Ok(())
    }

    /// 在同一事务内原子占用一个邮箱创建名额。
    pub async fn reserve_mailbox<C>(db: &C, user_id: &str, limit: i32) -> Result<()>
    where
        C: ConnectionTrait,
    {
        if limit <= 0 {
            anyhow::bail!("daily mailbox limit reached");
        }
        Self::ensure_today(db, user_id).await?;
        let updated = daily_quota::Entity::update_many()
            .col_expr(
                daily_quota::Column::MailboxesCreated,
                Expr::col(daily_quota::Column::MailboxesCreated).add(1),
            )
            .col_expr(
                daily_quota::Column::UpdatedAt,
                Expr::value(chrono::Utc::now().naive_utc()),
            )
            .filter(daily_quota::Column::UserId.eq(user_id))
            .filter(daily_quota::Column::Date.eq(Self::today()))
            .filter(daily_quota::Column::MailboxesCreated.lt(limit))
            .exec(db)
            .await?;
        if updated.rows_affected != 1 {
            anyhow::bail!("daily mailbox limit reached");
        }
        Ok(())
    }

    /// 在同一事务内原子占用一个收信名额并累计字节数。
    pub async fn reserve_email<C>(db: &C, user_id: &str, bytes: i64, limit: i32) -> Result<()>
    where
        C: ConnectionTrait,
    {
        if limit <= 0 {
            anyhow::bail!("daily email limit reached");
        }
        Self::ensure_today(db, user_id).await?;
        let updated = daily_quota::Entity::update_many()
            .col_expr(
                daily_quota::Column::EmailsReceived,
                Expr::col(daily_quota::Column::EmailsReceived).add(1),
            )
            .col_expr(
                daily_quota::Column::BytesReceived,
                Expr::col(daily_quota::Column::BytesReceived).add(bytes),
            )
            .col_expr(
                daily_quota::Column::UpdatedAt,
                Expr::value(chrono::Utc::now().naive_utc()),
            )
            .filter(daily_quota::Column::UserId.eq(user_id))
            .filter(daily_quota::Column::Date.eq(Self::today()))
            .filter(daily_quota::Column::EmailsReceived.lt(limit))
            .exec(db)
            .await?;
        if updated.rows_affected != 1 {
            anyhow::bail!("daily email limit reached");
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::database::{DatabaseConfig, DatabaseType};
    use sea_orm::TransactionTrait;

    async fn test_database() -> DatabaseConnection {
        let path = std::env::temp_dir().join(format!(
            "email-unlimit-quota-{}.sqlite",
            uuid::Uuid::new_v4()
        ));
        crate::db::init_database(&DatabaseConfig {
            database_type: DatabaseType::SQLite,
            host: None,
            port: None,
            user: None,
            password: None,
            database: None,
            path: Some(path),
            max_connections: 4,
        })
        .await
        .unwrap()
    }

    #[tokio::test]
    async fn reserves_up_to_limit_atomically() {
        let db = test_database().await;
        QuotaRepository::reserve_mailbox(&db, "user-1", 2)
            .await
            .unwrap();
        QuotaRepository::reserve_mailbox(&db, "user-1", 2)
            .await
            .unwrap();
        assert!(QuotaRepository::reserve_mailbox(&db, "user-1", 2)
            .await
            .is_err());

        let quota = QuotaRepository::new(db)
            .get_today("user-1")
            .await
            .unwrap()
            .unwrap();
        assert_eq!(quota.mailboxes_created, 2);
    }

    #[tokio::test]
    async fn reservation_rolls_back_with_caller_transaction() {
        let db = test_database().await;
        let txn = db.begin().await.unwrap();
        QuotaRepository::reserve_email(&txn, "user-2", 1024, 5)
            .await
            .unwrap();
        txn.rollback().await.unwrap();

        assert!(QuotaRepository::new(db)
            .get_today("user-2")
            .await
            .unwrap()
            .is_none());
    }
}
