//! 管理后台接口（RBAC：require_admin）。

use crate::domain::entities::{blocked_ip, blocked_sender, email, users};
use crate::domain::vo::ApiResponse;
use crate::error::ErrorResponse;
use crate::handlers::admin_governance::admin_audit;
use crate::infra::middleware::UserId;
use crate::repositories::{
    blacklist_repository::BlacklistRepository, quota_repository::QuotaRepository,
};
use crate::services::{
    credit_rule_service::{CreditRuleService, CreditRuleValues},
    credit_service::CreditService,
};
use crate::AppState;
use axum::{
    extract::{Path, Query, State},
    Json,
};
use sea_orm::{
    ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
    QuerySelect, Set, TransactionTrait,
};
use serde::{Deserialize, Serialize};

// ===== 用户管理 =====

#[derive(Deserialize)]
pub struct UpdateUserBody {
    pub role: Option<String>,
    pub status: Option<String>,
}

pub async fn update_user(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Path(id): Path<String>,
    Json(body): Json<UpdateUserBody>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    if id == operator_id && (body.role.is_some() || body.status.is_some()) {
        return Err(ErrorResponse::bad_request("不能修改当前登录账号"));
    }
    let u = users::Entity::find_by_id(&id)
        .one(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?
        .ok_or_else(|| ErrorResponse::not_found("user not found"))?;
    let previous_role = u.role.clone();
    let previous_status = u.status.clone();
    let mut a: users::ActiveModel = u.into();
    if let Some(r) = body.role {
        if !matches!(r.as_str(), "user" | "admin") {
            return Err(ErrorResponse::bad_request("invalid role"));
        }
        a.role = Set(r);
    }
    if let Some(s) = body.status {
        if !matches!(s.as_str(), "active" | "suspended" | "banned") {
            return Err(ErrorResponse::bad_request("invalid status"));
        }
        a.status = Set(s);
    }
    let updated = a
        .update(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    admin_audit(
        &state,
        &operator_id,
        "admin_user_change",
        "update",
        Some("单个用户属性修改"),
        "user",
        &id,
        Some(serde_json::json!({
            "before": {"role": previous_role, "status": previous_status},
            "after": {"role": updated.role, "status": updated.status},
        })),
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message((), "updated")))
}

// ===== 黑名单：发件人 =====

pub async fn list_blocked_senders(
    State(state): State<AppState>,
) -> Result<Json<ApiResponse<Vec<serde_json::Value>>>, ErrorResponse> {
    let list = BlacklistRepository::new(state.pool.clone())
        .list_senders()
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let value: Vec<_> = list
        .into_iter()
        .map(|b| {
            serde_json::json!({
                "id": b.id, "kind": b.kind, "value": b.value,
                "reason": b.reason, "source": b.source,
                "expires_at": b.expires_at.map(|t| t.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()),
            })
        })
        .collect();
    Ok(Json(ApiResponse::success(value)))
}

#[derive(Deserialize)]
pub struct AddBlockedSenderBody {
    pub kind: String,
    pub value: String,
    #[serde(default)]
    pub reason: String,
    pub ttl_seconds: Option<i64>,
}

pub async fn add_blocked_sender(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Json(body): Json<AddBlockedSenderBody>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    let expires_at = body
        .ttl_seconds
        .map(|t| chrono::Utc::now().naive_utc() + chrono::Duration::seconds(t));
    let reason = body.reason.clone();
    let model = BlacklistRepository::new(state.pool.clone())
        .add_sender(
            body.kind,
            body.value,
            body.reason,
            "manual".into(),
            expires_at,
        )
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    admin_audit(
        &state,
        &operator_id,
        "admin_blacklist_change",
        "add",
        Some(&reason),
        "blocked_sender",
        model.id,
        None,
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message((), "added")))
}

pub async fn delete_blocked_sender(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Path(id): Path<i64>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    BlacklistRepository::new(state.pool.clone())
        .delete_sender(id)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    admin_audit(
        &state,
        &operator_id,
        "admin_blacklist_change",
        "remove",
        Some("兼容接口解除黑名单"),
        "blocked_sender",
        id,
        None,
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message((), "deleted")))
}

// ===== 黑名单：IP =====

pub async fn list_blocked_ips(
    State(state): State<AppState>,
) -> Result<Json<ApiResponse<Vec<serde_json::Value>>>, ErrorResponse> {
    let list = BlacklistRepository::new(state.pool.clone())
        .list_ips()
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let value: Vec<_> = list
        .into_iter()
        .map(|b| {
            serde_json::json!({
                "id": b.id, "ip": b.ip, "reason": b.reason, "source": b.source,
                "expires_at": b.expires_at.map(|t| t.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()),
            })
        })
        .collect();
    Ok(Json(ApiResponse::success(value)))
}

#[derive(Deserialize)]
pub struct AddBlockedIpBody {
    pub ip: String,
    #[serde(default)]
    pub reason: String,
    pub ttl_seconds: Option<i64>,
}

pub async fn add_blocked_ip(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Json(body): Json<AddBlockedIpBody>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    let expires_at = body
        .ttl_seconds
        .map(|t| chrono::Utc::now().naive_utc() + chrono::Duration::seconds(t));
    let reason = body.reason.clone();
    let model = BlacklistRepository::new(state.pool.clone())
        .add_ip(body.ip, body.reason, "manual".into(), expires_at)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    admin_audit(
        &state,
        &operator_id,
        "admin_blacklist_change",
        "add",
        Some(&reason),
        "blocked_ip",
        model.id,
        None,
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message((), "added")))
}

pub async fn delete_blocked_ip(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Path(id): Path<i64>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    BlacklistRepository::new(state.pool.clone())
        .delete_ip(id)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    admin_audit(
        &state,
        &operator_id,
        "admin_blacklist_change",
        "remove",
        Some("兼容接口解除黑名单"),
        "blocked_ip",
        id,
        None,
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message((), "deleted")))
}

// ===== 统计 =====

pub async fn stats(
    State(state): State<AppState>,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let user_count = users::Entity::find()
        .filter(users::Column::DeletedAt.is_null())
        .count(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let email_count = email::Entity::find()
        .count(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let quarantined = email::Entity::find()
        .filter(email::Column::Status.eq("quarantined"))
        .count(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let blocked_senders = blocked_sender::Entity::find()
        .count(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let blocked_ips = blocked_ip::Entity::find()
        .count(&state.pool)
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success(serde_json::json!({
        "users": user_count,
        "emails": email_count,
        "quarantined": quarantined,
        "blocked_senders": blocked_senders,
        "blocked_ips": blocked_ips,
    }))))
}

// ===== 积分调整 =====

#[derive(Deserialize)]
pub struct AdjustCreditsBody {
    pub user_id: String,
    pub delta: i64,
    #[serde(default)]
    pub reason: String,
}

pub async fn adjust_credits(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Json(body): Json<AdjustCreditsBody>,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let new_balance = CreditService::new(state.pool.clone())
        .adjust(&body.user_id, body.delta, &body.reason, &operator_id)
        .await
        .map_err(|e| ErrorResponse::bad_request(e.to_string()))?;
    admin_audit(
        &state,
        &operator_id,
        "admin_credit_adjust",
        "adjust",
        Some(&body.reason),
        "user",
        &body.user_id,
        Some(serde_json::json!({"delta": body.delta, "balance_after": new_balance})),
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success(
        serde_json::json!({ "user_id": body.user_id, "balance": new_balance }),
    )))
}

// ===== 积分规则 =====

#[derive(Deserialize)]
pub struct UpdateCreditRulesBody {
    #[serde(flatten)]
    pub values: CreditRuleValues,
    pub expected_version: i64,
    pub reason: String,
}

#[derive(Deserialize)]
pub struct ResetCreditRulesBody {
    pub expected_version: i64,
    pub reason: String,
}

pub async fn get_credit_settings(
    State(state): State<AppState>,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let current = CreditRuleService::new(state.pool.clone())
        .get()
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success(serde_json::json!({
        "defaults": CreditRuleValues::default(),
        "current": current,
        "timezone": "Asia/Shanghai",
    }))))
}

pub async fn update_credit_settings(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Json(body): Json<UpdateCreditRulesBody>,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let result = CreditRuleService::new(state.pool.clone())
        .update(
            body.values,
            body.expected_version,
            &operator_id,
            &body.reason,
        )
        .await
        .map_err(|error| {
            let message = error.to_string();
            if message.contains("版本冲突") {
                ErrorResponse::conflict(message)
            } else {
                ErrorResponse::bad_request(message)
            }
        })?;
    admin_audit(
        &state,
        &operator_id,
        "admin_credit_rule_change",
        "update",
        Some(&body.reason),
        "credit_rule",
        1,
        Some(serde_json::json!({"version": result.version})),
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success(serde_json::json!({
        "current": result,
        "defaults": CreditRuleValues::default(),
        "timezone": "Asia/Shanghai",
    }))))
}

pub async fn reset_credit_settings(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Json(body): Json<ResetCreditRulesBody>,
) -> Result<Json<ApiResponse<serde_json::Value>>, ErrorResponse> {
    let result = CreditRuleService::new(state.pool.clone())
        .reset(body.expected_version, &operator_id, &body.reason)
        .await
        .map_err(|error| {
            let message = error.to_string();
            if message.contains("版本冲突") {
                ErrorResponse::conflict(message)
            } else {
                ErrorResponse::bad_request(message)
            }
        })?;
    admin_audit(
        &state,
        &operator_id,
        "admin_credit_rule_change",
        "reset",
        Some(&body.reason),
        "credit_rule",
        1,
        Some(serde_json::json!({"version": result.version})),
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success(serde_json::json!({
        "current": result,
        "defaults": CreditRuleValues::default(),
        "timezone": "Asia/Shanghai",
    }))))
}

// ===== 隔离区放行 =====

#[derive(Deserialize)]
pub struct QuarantinedEmailsQuery {
    pub limit: Option<u64>,
    pub offset: Option<u64>,
}

#[derive(Serialize)]
pub struct QuarantinedEmailItem {
    pub id: i64,
    pub recipient: String,
    pub mail_from: String,
    pub subject: Option<String>,
    pub abuse_score: i32,
    pub size_bytes: i64,
    pub received_at: String,
}

pub async fn list_quarantined_emails(
    State(state): State<AppState>,
    Query(query): Query<QuarantinedEmailsQuery>,
) -> Result<Json<ApiResponse<Vec<QuarantinedEmailItem>>>, ErrorResponse> {
    let limit = query.limit.unwrap_or(50).clamp(1, 100);
    let offset = query.offset.unwrap_or(0);
    let items = email::Entity::find()
        .filter(email::Column::Status.eq("quarantined"))
        .order_by_desc(email::Column::ReceivedAt)
        .offset(offset)
        .limit(limit)
        .all(&state.pool)
        .await
        .map_err(|error| ErrorResponse::internal(error.to_string()))?
        .into_iter()
        .map(|message| QuarantinedEmailItem {
            id: message.id,
            recipient: message.recipient,
            mail_from: message.mail_from,
            subject: message.subject,
            abuse_score: message.abuse_score,
            size_bytes: message.size_bytes,
            received_at: message
                .received_at
                .format("%Y-%m-%dT%H:%M:%S%.3fZ")
                .to_string(),
        })
        .collect();
    Ok(Json(ApiResponse::success(items)))
}

pub async fn release_email(
    State(state): State<AppState>,
    UserId(operator_id): UserId,
    Path(id): Path<i64>,
) -> Result<Json<ApiResponse<()>>, ErrorResponse> {
    let rules = CreditRuleService::new(state.pool.clone())
        .get()
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let txn = state
        .pool
        .begin()
        .await
        .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    let operation = async {
        let message = email::Entity::find_by_id(id)
            .one(&txn)
            .await?
            .ok_or_else(|| anyhow::anyhow!("邮件不存在"))?;
        if message.status != "quarantined" {
            anyhow::bail!("邮件不在隔离区");
        }
        QuotaRepository::reserve_email(
            &txn,
            &message.user_id,
            message.size_bytes,
            rules.daily_emails_limit,
        )
        .await?;
        CreditService::new(state.pool.clone())
            .debit_in_transaction(
                &txn,
                &message.user_id,
                rules.receive_email_cost,
                "receive_email",
                Some(message.mailbox_id),
                Some(message.id),
            )
            .await?;
        let mut active: email::ActiveModel = message.clone().into();
        active.status = Set("received".into());
        active.update(&txn).await?;
        Ok::<_, anyhow::Error>(message)
    }
    .await;
    let message = match operation {
        Ok(message) => {
            txn.commit()
                .await
                .map_err(|e| ErrorResponse::internal(e.to_string()))?;
            message
        }
        Err(error) => {
            if let Err(rollback_error) = txn.rollback().await {
                tracing::error!(%rollback_error, "隔离邮件放行事务回滚失败");
            }
            return Err(ErrorResponse::bad_request(error.to_string()));
        }
    };
    let payload = serde_json::json!({
        "event": "new_email",
        "data": {
            "id": message.id,
            "mail_from": message.mail_from,
            "sender_name": message.sender_name,
            "subject": message.subject,
            "size_bytes": message.size_bytes,
            "received_at": message.received_at.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
        }
    });
    state
        .mail_hub
        .notify(&message.recipient, &payload.to_string());
    admin_audit(
        &state,
        &operator_id,
        "admin_email_action",
        "release",
        Some("单封隔离邮件放行"),
        "email",
        id,
        None,
    )
    .await
    .map_err(|e| ErrorResponse::internal(e.to_string()))?;
    Ok(Json(ApiResponse::success_with_message((), "released")))
}
