use anyhow::Result;
use argon2::{
    password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2,
};
use rand::Rng;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait};

use crate::config::auth::AuthConfig;
use crate::domain::dto::auth::{DeleteUserRequest, LoginRequest, RegisterRequest};
use crate::domain::entities::{credit_account, credit_transaction, mailbox, users};
use crate::infra::redis::{
    redis_client::RedisClient,
    redis_key::{BusinessType, RedisKey},
};
use crate::repositories::user_repository::UserRepository;
use crate::utils::jwt::TokenService;

fn registration_role(config: &AuthConfig, email: &str) -> &'static str {
    if !config.bootstrap_admin_email.is_empty()
        && email.eq_ignore_ascii_case(config.bootstrap_admin_email.trim())
    {
        "admin"
    } else {
        "user"
    }
}

pub struct AuthService {
    user_repo: UserRepository,
    redis_client: Option<RedisClient>,
    auth_config: AuthConfig,
}

impl AuthService {
    pub fn new(
        user_repo: UserRepository,
        redis_client: Option<RedisClient>,
        auth_config: AuthConfig,
    ) -> Self {
        Self {
            user_repo,
            redis_client,
            auth_config,
        }
    }

    /// 哈希密码
    pub fn hash_password(password: &str) -> Result<String> {
        let salt = SaltString::generate(&mut OsRng);
        let argon2 = Argon2::default();
        let password_hash = argon2
            .hash_password(password.as_bytes(), &salt)
            .map_err(|e| anyhow::anyhow!("密码哈希失败: {}", e))?
            .to_string();
        Ok(password_hash)
    }

    /// 生成用户 ID
    pub fn generate_user_id(&self) -> String {
        let mut rng = rand::thread_rng();
        rng.gen_range(1_000_000_000i64..10_000_000_000i64)
            .to_string()
    }

    /// 生成唯一的用户 ID
    pub async fn generate_unique_user_id(&self) -> Result<String> {
        let mut attempts = 0;
        const MAX_ATTEMPTS: u32 = 10;

        loop {
            let candidate_id = self.generate_user_id();

            let existing = self.user_repo.count_by_id(&candidate_id).await?;
            if existing == 0 {
                return Ok(candidate_id);
            }

            attempts += 1;
            if attempts >= MAX_ATTEMPTS {
                return Err(anyhow::anyhow!("生成唯一用户 ID 失败"));
            }
        }
    }

    /// 保存 refresh_token 到 Redis
    async fn save_refresh_token(
        &self,
        user_id: &str,
        refresh_token: &str,
        expiration_days: i64,
    ) -> Result<()> {
        let key = RedisKey::new(BusinessType::Auth)
            .add_identifier("refresh_token")
            .add_identifier(user_id);

        let expiration_seconds = expiration_days * 24 * 3600;

        self.redis_client
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Redis 服务不可用"))?
            .set_ex(&key.build(), refresh_token, expiration_seconds as u64)
            .await
            .map_err(|e| anyhow::anyhow!("Redis 保存失败: {}", e))?;

        Ok(())
    }

    /// 获取并删除 refresh_token
    async fn get_and_delete_refresh_token(&self, user_id: &str) -> Result<String> {
        let key = RedisKey::new(BusinessType::Auth)
            .add_identifier("refresh_token")
            .add_identifier(user_id);

        let redis = self
            .redis_client
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Redis 服务不可用"))?;
        let token: Option<String> = redis
            .get(&key.build())
            .await
            .map_err(|e| anyhow::anyhow!("Redis 查询失败: {}", e))?;

        if token.is_some() {
            redis
                .delete_key(&key)
                .await
                .map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
        }

        token.ok_or_else(|| anyhow::anyhow!("刷新令牌无效或已过期"))
    }

    /// 删除用户的 refresh_token
    pub async fn delete_refresh_token(&self, user_id: &str) -> Result<()> {
        let key = RedisKey::new(BusinessType::Auth)
            .add_identifier("refresh_token")
            .add_identifier(user_id);

        self.redis_client
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Redis 服务不可用"))?
            .delete_key(&key)
            .await
            .map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;

        Ok(())
    }

    /// 注册用户
    pub async fn register(
        &self,
        request: RegisterRequest,
        register_bonus: i64,
    ) -> Result<(users::Model, String, String)> {
        // 0. 注册防御
        if !self.auth_config.registration_open {
            return Err(anyhow::anyhow!("注册已关闭"));
        }
        if !self.auth_config.invite_code.is_empty()
            && request.invite_code.as_deref() != Some(self.auth_config.invite_code.as_str())
        {
            return Err(anyhow::anyhow!("邀请码无效"));
        }

        let email = request.email.trim().to_lowercase();

        // 1. 检查邮箱是否已存在
        let existing = self.user_repo.count_by_email(&email).await?;

        if existing > 0 {
            return Err(anyhow::anyhow!("邮箱已注册"));
        }

        // 2. 哈希密码
        let password_hash = Self::hash_password(&request.password)?;

        // 3. 生成用户 ID
        let user_id = self.generate_unique_user_id().await?;

        // 4. 插入数据库并获取包含真实 created_at 的用户对象
        let role = registration_role(&self.auth_config, &email);
        let db = self.user_repo.connection();
        let txn = db.begin().await?;
        let operation = async {
            let user =
                UserRepository::insert_with(&txn, user_id.clone(), email, password_hash, role)
                    .await?;

            credit_account::ActiveModel {
                user_id: Set(user_id.clone()),
                balance: Set(register_bonus),
                version: Set(0),
                total_granted: Set(register_bonus),
                total_consumed: Set(0),
                ..Default::default()
            }
            .insert(&txn)
            .await?;
            credit_transaction::ActiveModel {
                user_id: Set(user_id.clone()),
                delta: Set(register_bonus),
                balance_after: Set(register_bonus),
                reason: Set("register_bonus".into()),
                related_mailbox_id: Set(None),
                related_email_id: Set(None),
                description: Set(None),
                operator_id: Set(None),
                ..Default::default()
            }
            .insert(&txn)
            .await?;

            Ok::<_, anyhow::Error>(user)
        }
        .await;
        let user = match operation {
            Ok(user) => {
                txn.commit().await?;
                user
            }
            Err(error) => {
                if let Err(rollback_error) = txn.rollback().await {
                    tracing::error!(%rollback_error, "注册事务回滚失败");
                }
                return Err(error);
            }
        };

        // 5. 生成 token
        let (access_token, refresh_token) = TokenService::generate_token_pair(
            &user_id,
            self.auth_config.access_token_expiration_minutes,
            self.auth_config.refresh_token_expiration_days,
            &self.auth_config.jwt_secret,
        )?;

        // 6. 保存 refresh_token
        if self.redis_client.is_some() {
            if let Err(error) = self
                .save_refresh_token(
                    &user_id,
                    &refresh_token,
                    self.auth_config.refresh_token_expiration_days,
                )
                .await
            {
                tracing::warn!(%error, user_id, "注册成功，但刷新令牌未能写入 Redis");
            }
        }

        Ok((user, access_token, refresh_token))
    }

    /// 登录
    pub async fn login(&self, request: LoginRequest) -> Result<(users::Model, String, String)> {
        // 1. 查询用户
        let user = self
            .user_repo
            .find_by_email(&request.email)
            .await?
            .ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
        if user.status != "active" {
            anyhow::bail!("账号已被停用");
        }

        // 2. 验证密码
        let password_hash = self
            .user_repo
            .get_password_hash(&request.email)
            .await?
            .ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;

        let parsed_hash = PasswordHash::new(&password_hash)
            .map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
        let argon2 = Argon2::default();

        argon2
            .verify_password(request.password.as_bytes(), &parsed_hash)
            .map_err(|_| anyhow::anyhow!("邮箱或密码错误"))?;

        // 3. 生成 token
        let (access_token, refresh_token) = TokenService::generate_token_pair(
            &user.id,
            self.auth_config.access_token_expiration_minutes,
            self.auth_config.refresh_token_expiration_days,
            &self.auth_config.jwt_secret,
        )?;

        // 4. 保存 refresh_token
        if self.redis_client.is_some() {
            if let Err(error) = self
                .save_refresh_token(
                    &user.id,
                    &refresh_token,
                    self.auth_config.refresh_token_expiration_days,
                )
                .await
            {
                tracing::warn!(%error, user_id = %user.id, "登录成功，但刷新令牌未能写入 Redis");
            }
        }

        Ok((user, access_token, refresh_token))
    }

    /// 使用 refresh_token 刷新 access_token
    pub async fn refresh_access_token(&self, refresh_token: &str) -> Result<(String, String)> {
        // 1. 从 refresh_token 中解码出 user_id
        let user_id =
            TokenService::decode_refresh_user_id(refresh_token, &self.auth_config.jwt_secret)?;

        // 2. 状态变更立即使刷新令牌失效。
        let user = self
            .user_repo
            .find_by_id_raw(&user_id)
            .await?
            .filter(|user| user.deleted_at.is_none())
            .ok_or_else(|| anyhow::anyhow!("用户不存在或已删除"))?;
        if user.status != "active" {
            anyhow::bail!("账号已被停用");
        }

        // 3. 从 Redis 获取存储的 token 并删除
        let stored_token = self.get_and_delete_refresh_token(&user_id).await?;

        // 4. 验证 token 是否匹配
        if stored_token != refresh_token {
            return Err(anyhow::anyhow!("刷新令牌无效"));
        }

        // 5. 生成新的 token 对
        let (new_access_token, new_refresh_token) = TokenService::generate_token_pair(
            &user_id,
            self.auth_config.access_token_expiration_minutes,
            self.auth_config.refresh_token_expiration_days,
            &self.auth_config.jwt_secret,
        )?;

        // 6. 保存新的 refresh_token
        self.save_refresh_token(
            &user_id,
            &new_refresh_token,
            self.auth_config.refresh_token_expiration_days,
        )
        .await?;

        Ok((new_access_token, new_refresh_token))
    }

    /// 删除用户
    pub async fn delete_user(&self, request: DeleteUserRequest) -> Result<()> {
        let password_hash = self
            .user_repo
            .get_password_hash_by_id(&request.user_id)
            .await?
            .ok_or_else(|| anyhow::anyhow!("用户不存在"))?;

        let parsed_hash = PasswordHash::new(&password_hash)
            .map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
        let argon2 = Argon2::default();

        argon2
            .verify_password(request.password.as_bytes(), &parsed_hash)
            .map_err(|_| anyhow::anyhow!("密码错误"))?;

        let db = self.user_repo.connection();
        let txn = db.begin().await?;
        let user = users::Entity::find_by_id(&request.user_id)
            .one(&txn)
            .await?
            .ok_or_else(|| anyhow::anyhow!("用户不存在"))?;
        let mut active: users::ActiveModel = user.into();
        active.deleted_at = Set(Some(chrono::Utc::now().naive_utc()));
        active.status = Set("deleted".into());
        active.update(&txn).await?;
        mailbox::Entity::update_many()
            .col_expr(
                mailbox::Column::Status,
                sea_orm::sea_query::Expr::value("revoked"),
            )
            .filter(mailbox::Column::UserId.eq(&request.user_id))
            .exec(&txn)
            .await?;
        txn.commit().await?;

        if self.redis_client.is_some() {
            if let Err(error) = self.delete_refresh_token(&request.user_id).await {
                tracing::warn!(
                    %error,
                    user_id = %request.user_id,
                    "账号已删除，但刷新令牌未能从 Redis 清理"
                );
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn hashes_password_with_random_salt() {
        let first = AuthService::hash_password("password123").unwrap();
        let second = AuthService::hash_password("password123").unwrap();
        assert_ne!(first, second);
        let parsed = PasswordHash::new(&first).unwrap();
        assert!(Argon2::default()
            .verify_password(b"password123", &parsed)
            .is_ok());
    }

    #[test]
    fn bootstrap_email_is_the_only_admin_registration() {
        let mut config = crate::config::app::AppConfig::load_from_path("config/development.toml")
            .unwrap()
            .auth;
        config.bootstrap_admin_email = "admin@example.com".into();
        assert_eq!(registration_role(&config, "ADMIN@example.com"), "admin");
        assert_eq!(registration_role(&config, "user@example.com"), "user");
    }
}
