use axum::Extension;
use axum::extract::{FromRef, State};
use axum::http::HeaderMap;
use axum::response::Json;
use validator::Validate;

use erp_core::error::AppError;
use erp_core::types::{ApiResponse, TenantContext};

use crate::auth_state::AuthState;
use crate::dto::{ChangePasswordReq, LoginReq, LoginResp, RefreshReq};
use crate::service::auth_service::{AuthService, JwtConfig, RequestInfo};

/// 从请求头中提取客户端信息。
fn extract_request_info(headers: &HeaderMap) -> RequestInfo {
    let ip = headers
        .get("x-forwarded-for")
        .or_else(|| headers.get("x-real-ip"))
        .and_then(|v| v.to_str().ok())
        .map(|s| s.split(',').next().unwrap_or(s).trim().to_string());
    let user_agent = headers
        .get("user-agent")
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());
    RequestInfo { ip, user_agent }
}

#[utoipa::path(
    post,
    path = "/api/v1/auth/login",
    request_body = LoginReq,
    responses(
        (status = 200, description = "登录成功", body = ApiResponse<LoginResp>),
        (status = 400, description = "请求参数错误"),
        (status = 401, description = "用户名或密码错误"),
    ),
    tag = "认证"
)]
/// POST /api/v1/auth/login
///
/// Authenticates a user with username and password, returning access and refresh tokens.
///
/// During the bootstrap phase, the tenant_id is taken from `AuthState::default_tenant_id`.
/// In production, this will come from a tenant-resolution middleware.
pub async fn login<S>(
    State(state): State<AuthState>,
    headers: HeaderMap,
    Json(req): Json<LoginReq>,
) -> Result<Json<ApiResponse<LoginResp>>, AppError>
where
    AuthState: FromRef<S>,
    S: Clone + Send + Sync + 'static,
{
    req.validate()
        .map_err(|e| AppError::Validation(e.to_string()))?;

    let req_info = extract_request_info(&headers);
    let tenant_id = state.default_tenant_id;

    let jwt_config = JwtConfig {
        secret: &state.jwt_secret,
        access_ttl_secs: state.access_ttl_secs,
        refresh_ttl_secs: state.refresh_ttl_secs,
    };

    let resp = AuthService::login(
        tenant_id,
        &req.username,
        &req.password,
        &state.db,
        &jwt_config,
        &state.event_bus,
        Some(&req_info),
        req.client_type.as_deref(),
    )
    .await?;

    Ok(Json(ApiResponse::ok(resp)))
}

#[utoipa::path(
    post,
    path = "/api/v1/auth/refresh",
    request_body = RefreshReq,
    responses(
        (status = 200, description = "刷新成功", body = ApiResponse<LoginResp>),
        (status = 401, description = "刷新令牌无效或已过期"),
    ),
    tag = "认证"
)]
/// POST /api/v1/auth/refresh
///
/// Validates an existing refresh token, revokes it (rotation), and issues
/// a new access + refresh token pair.
pub async fn refresh<S>(
    State(state): State<AuthState>,
    Json(req): Json<RefreshReq>,
) -> Result<Json<ApiResponse<LoginResp>>, AppError>
where
    AuthState: FromRef<S>,
    S: Clone + Send + Sync + 'static,
{
    let jwt_config = JwtConfig {
        secret: &state.jwt_secret,
        access_ttl_secs: state.access_ttl_secs,
        refresh_ttl_secs: state.refresh_ttl_secs,
    };

    let resp = AuthService::refresh(&req.refresh_token, &state.db, &jwt_config).await?;

    Ok(Json(ApiResponse::ok(resp)))
}

#[utoipa::path(
    post,
    path = "/api/v1/auth/logout",
    responses(
        (status = 200, description = "已成功登出"),
        (status = 401, description = "未授权"),
    ),
    security(("bearer_auth" = [])),
    tag = "认证"
)]
/// POST /api/v1/auth/logout
///
/// Revokes all refresh tokens for the authenticated user, effectively
/// logging them out on all devices.
pub async fn logout<S>(
    State(state): State<AuthState>,
    headers: HeaderMap,
    Extension(ctx): Extension<TenantContext>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
    AuthState: FromRef<S>,
    S: Clone + Send + Sync + 'static,
{
    let req_info = extract_request_info(&headers);
    AuthService::logout(ctx.user_id, ctx.tenant_id, &state.db, Some(&req_info)).await?;

    Ok(Json(ApiResponse {
        success: true,
        data: None,
        message: Some("已成功登出".to_string()),
    }))
}

#[utoipa::path(
    post,
    path = "/api/v1/auth/change-password",
    request_body = ChangePasswordReq,
    responses(
        (status = 200, description = "密码修改成功，需重新登录"),
        (status = 400, description = "当前密码不正确"),
        (status = 401, description = "未授权"),
    ),
    security(("bearer_auth" = [])),
    tag = "认证"
)]
/// POST /api/v1/auth/change-password
///
/// 修改当前登录用户的密码。修改成功后所有已签发的 refresh token 将被吊销，
/// 用户需要在所有设备上重新登录。
pub async fn change_password<S>(
    State(state): State<AuthState>,
    headers: HeaderMap,
    Extension(ctx): Extension<TenantContext>,
    Json(req): Json<ChangePasswordReq>,
) -> Result<Json<ApiResponse<()>>, AppError>
where
    AuthState: FromRef<S>,
    S: Clone + Send + Sync + 'static,
{
    req.validate()
        .map_err(|e| AppError::Validation(e.to_string()))?;

    let req_info = extract_request_info(&headers);
    AuthService::change_password(
        ctx.user_id,
        ctx.tenant_id,
        &req.current_password,
        &req.new_password,
        &state.db,
        Some(&req_info),
    )
    .await?;

    Ok(Json(ApiResponse {
        success: true,
        data: None,
        message: Some("密码修改成功，请重新登录".to_string()),
    }))
}
