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

use crate::types::{ApiErrorDetails, ApiResponse, LocalizedErrorMessages};

#[derive(Debug, Clone)]
pub struct AppError {
    pub(crate) status: StatusCode,
    pub(crate) code: &'static str,
    pub(crate) message_en: String,
    pub(crate) message_zh: String,
    pub(crate) operation: &'static str,
    pub(crate) entity: Option<&'static str>,
    pub(crate) field: Option<&'static str>,
    pub(crate) hint: Option<String>,
    pub(crate) source: Option<String>,
}

impl AppError {
    pub(crate) fn new(
        status: StatusCode,
        code: &'static str,
        operation: &'static str,
        message_en: impl Into<String>,
        message_zh: impl Into<String>,
    ) -> Self {
        Self {
            status,
            code,
            message_en: message_en.into(),
            message_zh: message_zh.into(),
            operation,
            entity: None,
            field: None,
            hint: None,
            source: None,
        }
    }

    pub(crate) fn with_entity(mut self, entity: &'static str) -> Self {
        self.entity = Some(entity);
        self
    }

    pub(crate) fn with_field(mut self, field: &'static str) -> Self {
        self.field = Some(field);
        self
    }

    pub(crate) fn with_hint(mut self, hint: impl Into<String>) -> Self {
        self.hint = Some(hint.into());
        self
    }

    pub(crate) fn with_source(mut self, source: impl Into<String>) -> Self {
        self.source = Some(source.into());
        self
    }

    pub(crate) fn bad_request(
        code: &'static str,
        operation: &'static str,
        message_en: impl Into<String>,
        message_zh: impl Into<String>,
    ) -> Self {
        Self::new(
            StatusCode::BAD_REQUEST,
            code,
            operation,
            message_en,
            message_zh,
        )
    }

    pub(crate) fn not_found(
        code: &'static str,
        operation: &'static str,
        message_en: impl Into<String>,
        message_zh: impl Into<String>,
    ) -> Self {
        Self::new(
            StatusCode::NOT_FOUND,
            code,
            operation,
            message_en,
            message_zh,
        )
    }

    pub(crate) fn conflict(
        code: &'static str,
        operation: &'static str,
        message_en: impl Into<String>,
        message_zh: impl Into<String>,
    ) -> Self {
        Self::new(
            StatusCode::CONFLICT,
            code,
            operation,
            message_en,
            message_zh,
        )
    }

    pub(crate) fn internal(
        code: &'static str,
        operation: &'static str,
        message_en: impl Into<String>,
        message_zh: impl Into<String>,
    ) -> Self {
        Self::new(
            StatusCode::INTERNAL_SERVER_ERROR,
            code,
            operation,
            message_en,
            message_zh,
        )
    }

    pub(crate) fn from_db(
        operation: &'static str,
        entity: &'static str,
        err: rusqlite::Error,
    ) -> Self {
        let status = match &err {
            rusqlite::Error::QueryReturnedNoRows => StatusCode::NOT_FOUND,
            rusqlite::Error::SqliteFailure(inner, _) => match inner.extended_code {
                787 | 1555 | 2067 => StatusCode::CONFLICT,
                _ => StatusCode::INTERNAL_SERVER_ERROR,
            },
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        };
        let (code, hint) = match &err {
            rusqlite::Error::QueryReturnedNoRows => (
                "db.not_found",
                format!("No {entity} record matched the requested identifier."),
            ),
            rusqlite::Error::SqliteFailure(inner, _) if inner.extended_code == 2067 => (
                "db.unique_violation",
                format!("A {entity} with the same unique value already exists."),
            ),
            rusqlite::Error::SqliteFailure(inner, _) if inner.extended_code == 1555 => (
                "db.primary_key_violation",
                format!("A {entity} with the same primary key already exists."),
            ),
            rusqlite::Error::SqliteFailure(inner, _) if inner.extended_code == 787 => (
                "db.foreign_key_violation",
                format!("The referenced {entity} relation does not exist."),
            ),
            _ => (
                "db.operation_failed",
                format!("Database operation '{operation}' failed for {entity}."),
            ),
        };
        Self::new(
            status,
            code,
            operation,
            format!("Database operation failed while processing {entity}."),
            format!("处理 {entity} 时数据库操作失败。"),
        )
        .with_entity(entity)
        .with_hint(hint)
        .with_source(err.to_string())
    }

    pub(crate) fn fallback_for_status(status: StatusCode) -> Self {
        match status {
            StatusCode::BAD_REQUEST => Self::bad_request(
                "request.invalid",
                "http.request",
                "The request is invalid.",
                "请求无效。",
            ),
            StatusCode::UNAUTHORIZED => Self::new(
                StatusCode::UNAUTHORIZED,
                "auth.invalid_api_key",
                "api_key_auth",
                "API key required or invalid.",
                "需要 API key 或 API key 无效。",
            ),
            StatusCode::NOT_FOUND => Self::not_found(
                "resource.not_found",
                "http.request",
                "The requested resource was not found.",
                "未找到请求的资源。",
            ),
            StatusCode::CONFLICT => Self::conflict(
                "request.conflict",
                "http.request",
                "The request conflicts with the current state.",
                "请求与当前状态冲突。",
            ),
            _ if status.is_client_error() => Self::new(
                status,
                "request.failed",
                "http.request",
                format!(
                    "HTTP {} {}",
                    status.as_u16(),
                    status.canonical_reason().unwrap_or("Client Error")
                ),
                format!(
                    "HTTP {} {}",
                    status.as_u16(),
                    status.canonical_reason().unwrap_or("客户端错误")
                ),
            ),
            _ => Self::internal(
                "server.internal",
                "http.request",
                "Internal server error.",
                "服务器内部错误。",
            ),
        }
    }

pub(crate) fn to_api_response<T: serde::Serialize>(&self) -> ApiResponse<T> {
        ApiResponse::err_with_details(
            self.message_en.clone(),
            self.code,
            ApiErrorDetails {
                operation: self.operation.to_string(),
                entity: self.entity.map(str::to_string),
                field: self.field.map(str::to_string),
                hint: self.hint.clone(),
            },
            LocalizedErrorMessages {
                en: self.message_en.clone(),
                zh: self.message_zh.clone(),
            },
        )
    }

    pub(crate) fn log(&self) {
        if self.status.is_server_error() {
            tracing::event!(
                tracing::Level::ERROR,
                error_code = self.code,
                http_status = self.status.as_u16(),
                operation = self.operation,
                entity = self.entity.unwrap_or(""),
                field = self.field.unwrap_or(""),
                hint = self.hint.as_deref().unwrap_or(""),
                source = self.source.as_deref().unwrap_or(""),
                message_en = %self.message_en,
                message_zh = %self.message_zh,
                "request failed"
            );
        } else if self.status == StatusCode::CONFLICT {
            tracing::event!(
                tracing::Level::WARN,
                error_code = self.code,
                http_status = self.status.as_u16(),
                operation = self.operation,
                entity = self.entity.unwrap_or(""),
                field = self.field.unwrap_or(""),
                hint = self.hint.as_deref().unwrap_or(""),
                source = self.source.as_deref().unwrap_or(""),
                message_en = %self.message_en,
                message_zh = %self.message_zh,
                "request failed"
            );
        } else {
            tracing::event!(
                tracing::Level::INFO,
                error_code = self.code,
                http_status = self.status.as_u16(),
                operation = self.operation,
                entity = self.entity.unwrap_or(""),
                field = self.field.unwrap_or(""),
                hint = self.hint.as_deref().unwrap_or(""),
                source = self.source.as_deref().unwrap_or(""),
                message_en = %self.message_en,
                message_zh = %self.message_zh,
                "request failed"
            );
        }
        if self.status.is_server_error() {
            let trace = std::backtrace::Backtrace::force_capture();
            tracing::error!(
                error_code = self.code,
                operation = self.operation,
                backtrace = %trace,
                "captured backtrace"
            );
        }
    }
}

pub(crate) fn error_response<T: serde::Serialize>(
    err: AppError,
) -> (StatusCode, Json<ApiResponse<T>>) {
    err.log();
    (err.status, Json(err.to_api_response::<T>()))
}

pub(crate) fn db_error_status(
    operation: &'static str,
    entity: &'static str,
    err: rusqlite::Error,
) -> StatusCode {
    let err = AppError::from_db(operation, entity, err);
    let status = err.status;
    err.log();
    status
}

pub(crate) fn db_error_response<T: serde::Serialize>(
    operation: &'static str,
    entity: &'static str,
    err: rusqlite::Error,
) -> (StatusCode, Json<ApiResponse<T>>) {
    error_response(AppError::from_db(operation, entity, err))
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let status = self.status;
        let json = Json(self.to_api_response::<()>());
        self.log();
        (status, json).into_response()
    }
}
