use axum::Json;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde::Serialize;

/// 统一错误响应格式
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: String,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<serde_json::Value>,
}

/// 平台级错误类型
#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("资源未找到: {0}")]
    NotFound(String),

    #[error("验证失败: {0}")]
    Validation(String),

    #[error("未授权")]
    Unauthorized,

    #[error("禁止访问: {0}")]
    Forbidden(String),

    #[error("冲突: {0}")]
    Conflict(String),

    #[error("版本冲突: 数据已被其他操作修改，请刷新后重试")]
    VersionMismatch,

    #[error("请求过于频繁，请稍后重试")]
    TooManyRequests,

    #[error("内部错误: {0}")]
    Internal(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match &self {
            AppError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()),
            AppError::Validation(_) => (StatusCode::BAD_REQUEST, self.to_string()),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "未授权".to_string()),
            AppError::Forbidden(_) => (StatusCode::FORBIDDEN, self.to_string()),
            AppError::Conflict(_) => (StatusCode::CONFLICT, self.to_string()),
            AppError::VersionMismatch => (StatusCode::CONFLICT, self.to_string()),
            AppError::TooManyRequests => (StatusCode::TOO_MANY_REQUESTS, self.to_string()),
            AppError::Internal(msg) => {
                tracing::error!("Internal error: {}", msg);
                (StatusCode::INTERNAL_SERVER_ERROR, "内部错误".to_string())
            }
        };

        let body = ErrorResponse {
            error: status.canonical_reason().unwrap_or("Error").to_string(),
            message,
            details: None,
        };

        (status, Json(body)).into_response()
    }
}

impl From<anyhow::Error> for AppError {
    fn from(err: anyhow::Error) -> Self {
        AppError::Internal(err.to_string())
    }
}

impl From<sea_orm::DbErr> for AppError {
    fn from(err: sea_orm::DbErr) -> Self {
        match err {
            sea_orm::DbErr::RecordNotFound(msg) => AppError::NotFound(msg),
            sea_orm::DbErr::Query(sea_orm::RuntimeErr::SqlxError(e))
                if e.to_string().contains("duplicate key") =>
            {
                AppError::Conflict("记录已存在".to_string())
            }
            _ => AppError::Internal(err.to_string()),
        }
    }
}

pub type AppResult<T> = Result<T, AppError>;

/// 检查乐观锁版本是否匹配。
///
/// 返回下一个版本号（actual + 1），或 VersionMismatch 错误。
pub fn check_version(expected: i32, actual: i32) -> AppResult<i32> {
    if expected == actual {
        Ok(actual + 1)
    } else {
        Err(AppError::VersionMismatch)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_version_ok() {
        assert_eq!(check_version(1, 1).unwrap(), 2);
        assert_eq!(check_version(5, 5).unwrap(), 6);
    }

    #[test]
    fn check_version_mismatch() {
        let result = check_version(1, 2);
        assert!(result.is_err());
        match result.unwrap_err() {
            AppError::VersionMismatch => {}
            other => panic!("Expected VersionMismatch, got {:?}", other),
        }
    }

    #[test]
    fn db_err_record_not_found_maps_to_not_found() {
        let err = sea_orm::DbErr::RecordNotFound("test".to_string());
        let app_err: AppError = err.into();
        match app_err {
            AppError::NotFound(msg) => assert_eq!(msg, "test"),
            other => panic!("Expected NotFound, got {:?}", other),
        }
    }

    #[test]
    fn db_err_generic_maps_to_internal() {
        let db_err = sea_orm::DbErr::Custom("some error".to_string());
        let app_err: AppError = db_err.into();
        match app_err {
            AppError::Internal(msg) => assert!(msg.contains("some error")),
            other => panic!("Expected Internal, got {:?}", other),
        }
    }

    #[test]
    fn app_error_into_response_status_codes() {
        // NotFound -> 404
        let resp = AppError::NotFound("test".to_string()).into_response();
        assert_eq!(resp.status(), StatusCode::NOT_FOUND);

        // Validation -> 400
        let resp = AppError::Validation("bad input".to_string()).into_response();
        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);

        // Unauthorized -> 401
        let resp = AppError::Unauthorized.into_response();
        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);

        // Forbidden -> 403
        let resp = AppError::Forbidden("no access".to_string()).into_response();
        assert_eq!(resp.status(), StatusCode::FORBIDDEN);

        // VersionMismatch -> 409
        let resp = AppError::VersionMismatch.into_response();
        assert_eq!(resp.status(), StatusCode::CONFLICT);

        // TooManyRequests -> 429
        let resp = AppError::TooManyRequests.into_response();
        assert_eq!(resp.status(), StatusCode::TOO_MANY_REQUESTS);

        // Internal -> 500
        let resp = AppError::Internal("oops".to_string()).into_response();
        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }

    #[test]
    fn app_error_internal_hides_details_from_response() {
        // Internal errors should map to 500 with a generic message
        let resp = AppError::Internal("sensitive db error detail".to_string()).into_response();
        assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
    }

    #[test]
    fn anyhow_error_maps_to_internal() {
        let err: AppError = anyhow::anyhow!("something went wrong").into();
        match err {
            AppError::Internal(msg) => assert_eq!(msg, "something went wrong"),
            other => panic!("Expected Internal, got {:?}", other),
        }
    }
}
