use chrono::Utc;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use uuid::Uuid;

use crate::dto::{LoginResp, RoleResp, UserResp};
use crate::entity::{role, user, user_credential, user_role};
use crate::error::AuthError;
use crate::middleware::revoke_all_user_tokens as revoke_access_token_cache;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::events::EventBus;

use crate::error::AuthResult;

use super::password;
use super::token_service::TokenService;

/// 请求来源信息，用于审计日志记录。
pub struct RequestInfo {
    pub ip: Option<String>,
    pub user_agent: Option<String>,
}

/// JWT configuration needed for token signing.
pub struct JwtConfig<'a> {
    pub secret: &'a str,
    pub access_ttl_secs: i64,
    pub refresh_ttl_secs: i64,
}

/// Authentication service handling login, token refresh, and logout.
pub struct AuthService;

impl AuthService {
    /// Authenticate a user and issue access + refresh tokens.
    ///
    /// Steps:
    /// 1. Look up user by tenant + username (soft-delete aware)
    /// 2. Verify user status is "active"
    /// 3. Fetch the stored password credential
    /// 4. Verify password hash
    /// 5. Collect roles and permissions
    /// 6. Sign JWT tokens
    /// 7. Update last_login_at
    /// 8. Publish login event
    #[allow(clippy::too_many_arguments)]
    pub async fn login(
        tenant_id: Uuid,
        username: &str,
        password_plain: &str,
        db: &sea_orm::DatabaseConnection,
        jwt: &JwtConfig<'_>,
        event_bus: &EventBus,
        req_info: Option<&RequestInfo>,
        client_type: Option<&str>,
    ) -> AuthResult<LoginResp> {
        // 1. Find user by tenant_id + username
        let user_model = match user::Entity::find()
            .filter(user::Column::TenantId.eq(tenant_id))
            .filter(user::Column::Username.eq(username))
            .filter(user::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
        {
            Some(m) => m,
            None => {
                // 审计：用户不存在（登录失败）
                audit_service::record(
                    AuditLog::new(tenant_id, None, "user.login_failed", "user").with_request_info(
                        req_info.as_ref().and_then(|r| r.ip.clone()),
                        req_info.as_ref().and_then(|r| r.user_agent.clone()),
                    ),
                    db,
                )
                .await;
                return Err(AuthError::InvalidCredentials);
            }
        };

        // 2. Check user status
        if user_model.status != "active" {
            return Err(AuthError::UserDisabled(user_model.status.clone()));
        }

        // 3. Find password credential
        let cred = user_credential::Entity::find()
            .filter(user_credential::Column::UserId.eq(user_model.id))
            .filter(user_credential::Column::CredentialType.eq("password"))
            .filter(user_credential::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .ok_or(AuthError::InvalidCredentials)?;

        // 4. Verify password
        let stored_hash = cred
            .credential_data
            .as_ref()
            .and_then(|v| v.get("hash").and_then(|h| h.as_str()))
            .ok_or(AuthError::InvalidCredentials)?;

        if !password::verify_password(password_plain, stored_hash)? {
            // 审计：密码错误（登录失败）
            audit_service::record(
                AuditLog::new(tenant_id, Some(user_model.id), "user.login_failed", "user")
                    .with_request_info(
                        req_info.as_ref().and_then(|r| r.ip.clone()),
                        req_info.as_ref().and_then(|r| r.user_agent.clone()),
                    ),
                db,
            )
            .await;
            return Err(AuthError::InvalidCredentials);
        }

        // 5. Get roles and permissions
        let roles: Vec<String> = TokenService::get_user_roles(user_model.id, tenant_id, db).await?;

        // 纯患者角色不允许登录管理端（同时拥有医护角色则放行）
        // 小程序端 (client_type=miniprogram) 允许患者登录
        let medical_roles = ["doctor", "nurse", "admin", "health_manager", "operator"];
        let is_pure_patient =
            roles.iter().all(|r| r == "patient") && roles.iter().any(|r| r == "patient");
        let has_medical_role = roles.iter().any(|r| medical_roles.contains(&r.as_str()));
        let is_miniprogram = client_type == Some("miniprogram");
        if is_pure_patient && !has_medical_role && !is_miniprogram {
            return Err(AuthError::Forbidden("患者账号请使用小程序登录".to_string()));
        }

        // 小程序端仅允许患者角色登录，医护角色请使用管理端
        let has_patient_role = roles.iter().any(|r| r == "patient");
        if is_miniprogram && !has_patient_role {
            return Err(AuthError::Forbidden("医护账号请使用管理端登录".to_string()));
        }

        let permissions = TokenService::get_user_permissions(user_model.id, tenant_id, db).await?;

        // 6. Sign tokens
        let access_token = TokenService::sign_access_token(
            user_model.id,
            tenant_id,
            roles.clone(),
            permissions,
            jwt.secret,
            jwt.access_ttl_secs,
        )?;
        let (refresh_token, _) = TokenService::sign_refresh_token(
            user_model.id,
            tenant_id,
            db,
            jwt.secret,
            jwt.refresh_ttl_secs,
        )
        .await?;

        // 7. Update last_login_at
        let mut user_active: user::ActiveModel = user_model.clone().into();
        user_active.last_login_at = Set(Some(Utc::now()));
        user_active.updated_at = Set(Utc::now());
        user_active.version = Set(user_active.version.take().unwrap_or(0) + 1);
        user_active
            .update(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        // 8. Build response
        let role_resps = Self::get_user_role_resps(user_model.id, tenant_id, db).await?;
        let user_resp = UserResp {
            id: user_model.id,
            username: user_model.username.clone(),
            email: user_model.email,
            phone: user_model.phone,
            display_name: user_model.display_name,
            avatar_url: user_model.avatar_url,
            status: user_model.status,
            roles: role_resps,
            version: user_model.version,
        };

        // 9. Publish event
        event_bus.publish(erp_core::events::DomainEvent::new(
            "user.login",
            tenant_id,
            erp_core::events::build_event_payload(serde_json::json!({ "user_id": user_model.id, "username": user_model.username })),
        ), db).await;

        // 审计：登录成功
        audit_service::record(
            AuditLog::new(tenant_id, Some(user_model.id), "user.login", "user")
                .with_resource_id(user_model.id)
                .with_request_info(
                    req_info.as_ref().and_then(|r| r.ip.clone()),
                    req_info.as_ref().and_then(|r| r.user_agent.clone()),
                ),
            db,
        )
        .await;

        Ok(LoginResp {
            access_token,
            refresh_token,
            expires_in: jwt.access_ttl_secs as u64,
            user: user_resp,
        })
    }

    /// Refresh the token pair: validate the old refresh token, revoke it, issue a new pair.
    pub async fn refresh(
        refresh_token_str: &str,
        db: &sea_orm::DatabaseConnection,
        jwt: &JwtConfig<'_>,
    ) -> AuthResult<LoginResp> {
        // Atomically validate and revoke the old refresh token (prevents TOCTOU race)
        let claims =
            TokenService::validate_and_revoke_atomic(refresh_token_str, db, jwt.secret).await?;

        // Fetch fresh roles and permissions
        let roles: Vec<String> = TokenService::get_user_roles(claims.sub, claims.tid, db).await?;
        let permissions = TokenService::get_user_permissions(claims.sub, claims.tid, db).await?;

        // Sign new token pair
        let access_token = TokenService::sign_access_token(
            claims.sub,
            claims.tid,
            roles.clone(),
            permissions,
            jwt.secret,
            jwt.access_ttl_secs,
        )?;
        let (new_refresh_token, _) = TokenService::sign_refresh_token(
            claims.sub,
            claims.tid,
            db,
            jwt.secret,
            jwt.refresh_ttl_secs,
        )
        .await?;

        // Fetch user for the response
        let user_model = user::Entity::find_by_id(claims.sub)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .ok_or(AuthError::TokenRevoked)?;

        // 验证用户属于 JWT 中声明的租户
        if user_model.tenant_id != claims.tid {
            tracing::warn!(
                user_id = %claims.sub,
                jwt_tenant = %claims.tid,
                actual_tenant = %user_model.tenant_id,
                "Token tenant_id 与用户实际租户不匹配"
            );
            return Err(AuthError::TokenRevoked);
        }

        let role_resps = Self::get_user_role_resps(claims.sub, claims.tid, db).await?;
        let user_resp = UserResp {
            id: user_model.id,
            username: user_model.username,
            email: user_model.email,
            phone: user_model.phone,
            display_name: user_model.display_name,
            avatar_url: user_model.avatar_url,
            status: user_model.status,
            roles: role_resps,
            version: user_model.version,
        };

        Ok(LoginResp {
            access_token,
            refresh_token: new_refresh_token,
            expires_in: jwt.access_ttl_secs as u64,
            user: user_resp,
        })
    }

    /// Revoke all refresh tokens for a user, effectively logging them out everywhere.
    pub async fn logout(
        user_id: Uuid,
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
        req_info: Option<&RequestInfo>,
    ) -> AuthResult<()> {
        TokenService::revoke_all_user_tokens(user_id, tenant_id, db).await?;

        // 清除 access token 权限缓存，强制重新认证
        revoke_access_token_cache(user_id);

        // 审计：登出
        audit_service::record(
            AuditLog::new(tenant_id, Some(user_id), "user.logout", "user")
                .with_resource_id(user_id)
                .with_request_info(
                    req_info.as_ref().and_then(|r| r.ip.clone()),
                    req_info.as_ref().and_then(|r| r.user_agent.clone()),
                ),
            db,
        )
        .await;

        Ok(())
    }

    /// Change password for the authenticated user.
    ///
    /// Steps:
    /// 1. Verify current password
    /// 2. Hash the new password
    /// 3. Update the credential record
    /// 4. Revoke all existing refresh tokens (force re-login)
    pub async fn change_password(
        user_id: Uuid,
        tenant_id: Uuid,
        current_password: &str,
        new_password: &str,
        db: &sea_orm::DatabaseConnection,
        req_info: Option<&RequestInfo>,
    ) -> AuthResult<()> {
        // 1. Find the user's password credential
        let cred = user_credential::Entity::find()
            .filter(user_credential::Column::UserId.eq(user_id))
            .filter(user_credential::Column::TenantId.eq(tenant_id))
            .filter(user_credential::Column::CredentialType.eq("password"))
            .filter(user_credential::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .ok_or_else(|| AuthError::Validation("用户凭证不存在".to_string()))?;

        // 2. Verify current password
        let stored_hash = cred
            .credential_data
            .as_ref()
            .and_then(|v| v.get("hash").and_then(|h| h.as_str()))
            .ok_or_else(|| AuthError::Validation("用户凭证异常".to_string()))?;

        if !password::verify_password(current_password, stored_hash)? {
            return Err(AuthError::Validation("当前密码不正确".to_string()));
        }

        // 3. Hash new password and update credential
        let new_hash = password::hash_password(new_password)?;
        let current_version = cred.version;
        let mut cred_active: user_credential::ActiveModel = cred.into();
        cred_active.credential_data = Set(Some(serde_json::json!({ "hash": new_hash })));
        cred_active.updated_at = Set(Utc::now());
        cred_active.version = Set(current_version + 1);
        cred_active
            .update(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        // 4. Revoke all refresh tokens — force re-login on all devices
        TokenService::revoke_all_user_tokens(user_id, tenant_id, db).await?;

        // 清除 access token 权限缓存，密码修改后所有已签发的 access token 强制失效
        revoke_access_token_cache(user_id);

        // 审计：密码修改
        audit_service::record(
            AuditLog::new(tenant_id, Some(user_id), "user.change_password", "user")
                .with_resource_id(user_id)
                .with_request_info(
                    req_info.as_ref().and_then(|r| r.ip.clone()),
                    req_info.as_ref().and_then(|r| r.user_agent.clone()),
                ),
            db,
        )
        .await;

        tracing::info!(user_id = %user_id, "Password changed successfully");
        Ok(())
    }

    /// Fetch role details for a user, returning RoleResp DTOs.
    pub async fn get_user_role_resps(
        user_id: Uuid,
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<Vec<RoleResp>> {
        let user_roles = user_role::Entity::find()
            .filter(user_role::Column::UserId.eq(user_id))
            .filter(user_role::Column::TenantId.eq(tenant_id))
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        let role_ids: Vec<Uuid> = user_roles.iter().map(|ur| ur.role_id).collect();
        if role_ids.is_empty() {
            return Ok(vec![]);
        }

        let roles = role::Entity::find()
            .filter(role::Column::Id.is_in(role_ids))
            .filter(role::Column::TenantId.eq(tenant_id))
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        Ok(roles
            .iter()
            .map(|r| RoleResp {
                id: r.id,
                name: r.name.clone(),
                code: r.code.clone(),
                description: r.description.clone(),
                is_system: r.is_system,
                version: r.version,
            })
            .collect())
    }
}
