use crate::domain::entities::{credit_rule, credit_rule_change};
use anyhow::Result;
use sea_orm::{
    sea_query::Expr, ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter,
    Set, TransactionTrait,
};
use serde::{Deserialize, Serialize};

pub const CREDIT_RULE_ID: i32 = 1;
pub const DEFAULT_REGISTER_BONUS: i64 = 30;
pub const DEFAULT_DAILY_CHECK_IN_REWARD: i64 = 3;
pub const DEFAULT_REWARD_BALANCE_CAP: i64 = 90;
pub const DEFAULT_CREATE_MAILBOX_COST: i64 = 2;
pub const DEFAULT_RECEIVE_EMAIL_COST: i64 = 1;
pub const DEFAULT_DAILY_MAILBOXES_LIMIT: i32 = 3;
pub const DEFAULT_DAILY_EMAILS_LIMIT: i32 = 20;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CreditRuleValues {
    pub register_bonus: i64,
    pub daily_check_in_reward: i64,
    pub reward_balance_cap: i64,
    pub create_mailbox_cost: i64,
    pub receive_email_cost: i64,
    pub daily_mailboxes_limit: i32,
    pub daily_emails_limit: i32,
}

impl Default for CreditRuleValues {
    fn default() -> Self {
        Self {
            register_bonus: DEFAULT_REGISTER_BONUS,
            daily_check_in_reward: DEFAULT_DAILY_CHECK_IN_REWARD,
            reward_balance_cap: DEFAULT_REWARD_BALANCE_CAP,
            create_mailbox_cost: DEFAULT_CREATE_MAILBOX_COST,
            receive_email_cost: DEFAULT_RECEIVE_EMAIL_COST,
            daily_mailboxes_limit: DEFAULT_DAILY_MAILBOXES_LIMIT,
            daily_emails_limit: DEFAULT_DAILY_EMAILS_LIMIT,
        }
    }
}

impl CreditRuleValues {
    pub fn validate(&self) -> Result<()> {
        if self.register_bonus < 0 {
            anyhow::bail!("注册奖励不能为负数");
        }
        if self.daily_check_in_reward < 0 {
            anyhow::bail!("签到奖励不能为负数");
        }
        if self.reward_balance_cap < self.register_bonus
            || self.reward_balance_cap < self.daily_check_in_reward
        {
            anyhow::bail!("奖励余额上限不能低于注册奖励或签到奖励");
        }
        if self.create_mailbox_cost < 1 || self.receive_email_cost < 1 {
            anyhow::bail!("邮箱创建和收信积分成本必须至少为 1");
        }
        if self.daily_mailboxes_limit < 1 || self.daily_emails_limit < 1 {
            anyhow::bail!("每日额度必须至少为 1");
        }
        Ok(())
    }
}

impl From<&credit_rule::Model> for CreditRuleValues {
    fn from(value: &credit_rule::Model) -> Self {
        Self {
            register_bonus: value.register_bonus,
            daily_check_in_reward: value.daily_check_in_reward,
            reward_balance_cap: value.reward_balance_cap,
            create_mailbox_cost: value.create_mailbox_cost,
            receive_email_cost: value.receive_email_cost,
            daily_mailboxes_limit: value.daily_mailboxes_limit,
            daily_emails_limit: value.daily_emails_limit,
        }
    }
}

pub struct CreditRuleService {
    db: DatabaseConnection,
}

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

    pub async fn ensure_defaults(db: &DatabaseConnection) -> Result<credit_rule::Model> {
        if let Some(rule) = credit_rule::Entity::find_by_id(CREDIT_RULE_ID)
            .one(db)
            .await?
        {
            return Ok(rule);
        }
        let defaults = CreditRuleValues::default();
        defaults.validate()?;
        let insert = credit_rule::ActiveModel {
            id: Set(CREDIT_RULE_ID),
            register_bonus: Set(defaults.register_bonus),
            daily_check_in_reward: Set(defaults.daily_check_in_reward),
            reward_balance_cap: Set(defaults.reward_balance_cap),
            create_mailbox_cost: Set(defaults.create_mailbox_cost),
            receive_email_cost: Set(defaults.receive_email_cost),
            daily_mailboxes_limit: Set(defaults.daily_mailboxes_limit),
            daily_emails_limit: Set(defaults.daily_emails_limit),
            version: Set(0),
            updated_by: Set(None),
            ..Default::default()
        }
        .insert(db)
        .await;
        match insert {
            Ok(rule) => Ok(rule),
            Err(_) => credit_rule::Entity::find_by_id(CREDIT_RULE_ID)
                .one(db)
                .await?
                .ok_or_else(|| anyhow::anyhow!("积分规则初始化失败")),
        }
    }

    pub async fn get(&self) -> Result<credit_rule::Model> {
        Self::ensure_defaults(&self.db).await
    }

    pub async fn update(
        &self,
        values: CreditRuleValues,
        expected_version: i64,
        operator_id: &str,
        reason: &str,
    ) -> Result<credit_rule::Model> {
        values.validate()?;
        let reason = reason.trim();
        if reason.is_empty() {
            anyhow::bail!("修改原因不能为空");
        }

        let txn = self.db.begin().await?;
        let current = credit_rule::Entity::find_by_id(CREDIT_RULE_ID)
            .one(&txn)
            .await?
            .ok_or_else(|| anyhow::anyhow!("积分规则不存在"))?;
        if current.version != expected_version {
            anyhow::bail!("积分规则版本冲突");
        }
        let next_version = current.version + 1;
        let updated = credit_rule::Entity::update_many()
            .col_expr(
                credit_rule::Column::RegisterBonus,
                Expr::value(values.register_bonus),
            )
            .col_expr(
                credit_rule::Column::DailyCheckInReward,
                Expr::value(values.daily_check_in_reward),
            )
            .col_expr(
                credit_rule::Column::RewardBalanceCap,
                Expr::value(values.reward_balance_cap),
            )
            .col_expr(
                credit_rule::Column::CreateMailboxCost,
                Expr::value(values.create_mailbox_cost),
            )
            .col_expr(
                credit_rule::Column::ReceiveEmailCost,
                Expr::value(values.receive_email_cost),
            )
            .col_expr(
                credit_rule::Column::DailyMailboxesLimit,
                Expr::value(values.daily_mailboxes_limit),
            )
            .col_expr(
                credit_rule::Column::DailyEmailsLimit,
                Expr::value(values.daily_emails_limit),
            )
            .col_expr(credit_rule::Column::Version, Expr::value(next_version))
            .col_expr(
                credit_rule::Column::UpdatedBy,
                Expr::value(Some(operator_id.to_string())),
            )
            .col_expr(
                credit_rule::Column::UpdatedAt,
                Expr::value(chrono::Utc::now().naive_utc()),
            )
            .filter(credit_rule::Column::Id.eq(CREDIT_RULE_ID))
            .filter(credit_rule::Column::Version.eq(expected_version))
            .exec(&txn)
            .await?;
        if updated.rows_affected != 1 {
            anyhow::bail!("积分规则版本冲突");
        }
        let next = credit_rule::Entity::find_by_id(CREDIT_RULE_ID)
            .one(&txn)
            .await?
            .ok_or_else(|| anyhow::anyhow!("积分规则更新失败"))?;
        credit_rule_change::ActiveModel {
            operator_id: Set(operator_id.into()),
            previous_json: Set(serde_json::to_string(&current)?),
            next_json: Set(serde_json::to_string(&next)?),
            reason: Set(reason.into()),
            ..Default::default()
        }
        .insert(&txn)
        .await?;
        txn.commit().await?;
        Ok(next)
    }

    pub async fn reset(
        &self,
        expected_version: i64,
        operator_id: &str,
        reason: &str,
    ) -> Result<credit_rule::Model> {
        self.update(
            CreditRuleValues::default(),
            expected_version,
            operator_id,
            reason,
        )
        .await
    }
}

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

    async fn test_database() -> DatabaseConnection {
        let path = std::env::temp_dir().join(format!(
            "email-unlimit-credit-rules-{}.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 defaults_persist_and_updates_use_optimistic_version() {
        let db = test_database().await;
        let initial = CreditRuleService::ensure_defaults(&db).await.unwrap();
        assert_eq!(initial.register_bonus, 30);
        assert_eq!(initial.create_mailbox_cost, 2);
        assert_eq!(initial.daily_emails_limit, 20);

        let svc = CreditRuleService::new(db);
        let mut values = CreditRuleValues::from(&initial);
        values.daily_emails_limit = 12;
        let updated = svc
            .update(values.clone(), 0, "admin-1", "收紧测试规则")
            .await
            .unwrap();
        assert_eq!(updated.version, 1);
        assert_eq!(updated.daily_emails_limit, 12);
        assert!(svc
            .update(values, 0, "admin-2", "并发旧版本")
            .await
            .unwrap_err()
            .to_string()
            .contains("版本冲突"));

        let reset = svc.reset(1, "admin-1", "恢复默认").await.unwrap();
        assert_eq!(reset.version, 2);
        assert_eq!(reset.daily_emails_limit, 20);
    }
}
