use crate::collection_mutexes::error::Error as MutexError;
use crate::metrics;
use crate::protocol::ApiResponse;
use anyhow::Error;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use log::*;
use model::collection::CollectionRecord;
use model::error::no_such_table;
use thiserror::Error;
use tokio::time::Instant;

pub(crate) type Result<R> = std::result::Result<R, QdrantError>;

#[derive(Error, Debug)]
pub enum QdrantError {
    #[error("Api-Key is missing or invalid")]
    Unauthorized,
    #[error("Bad request")]
    BadRequest,
    #[error("Bad request: {0}")]
    BadRequestWithMsg(String),
    #[error("Internal server error")]
    InternalServerError,
    #[error("Internal server error: {0}")]
    InternalServerErrorWithMsg(String),
    #[error("Collection not found")]
    CollectionNotFound,
    #[error("[trace-id:{0}] Collection not found")]
    CollectionNotFoundWithTraceId(String),
    #[error("Point not found")]
    PointNotFound,
    #[error("Not implemented. If you need this feature, please describe your scenario and tell us at https://www.pingcap.com/contact-us/.")]
    NotImplemented,
    #[error("Entry existed")]
    Conflict,
    #[error("Conflict: {0}")]
    ConflictWithMessage(String),
}

pub(crate) fn bad_request(msg: String) -> QdrantError {
    QdrantError::BadRequestWithMsg(msg)
}

pub(crate) fn validate_collection_name<T>(collection_name: T) -> Result<T>
where
    T: AsRef<str>,
{
    let name = collection_name.as_ref();
    if CollectionRecord::is_valid_name(name) {
        Ok(collection_name)
    } else {
        Err(bad_request(format!("invalid collection name '{}'", name)))
    }
}

pub(crate) fn internal_server_error<I, T>(msg: String, log: I) -> QdrantError
where
    I: Into<Option<T>>,
    T: ToString,
{
    if let Some(log) = log.into() {
        error!("{}", log.to_string());
    }
    if msg.is_empty() {
        QdrantError::InternalServerError
    } else {
        QdrantError::InternalServerErrorWithMsg(msg)
    }
}

impl IntoResponse for QdrantError {
    fn into_response(self) -> Response {
        let status_code = match &self {
            QdrantError::Unauthorized => StatusCode::UNAUTHORIZED,
            QdrantError::BadRequest => StatusCode::BAD_REQUEST,
            QdrantError::BadRequestWithMsg(_) => StatusCode::BAD_REQUEST,
            QdrantError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
            QdrantError::InternalServerErrorWithMsg(_) => StatusCode::INTERNAL_SERVER_ERROR,
            QdrantError::CollectionNotFound => StatusCode::NOT_FOUND,
            QdrantError::CollectionNotFoundWithTraceId(_) => StatusCode::NOT_FOUND,
            QdrantError::PointNotFound => StatusCode::NOT_FOUND,
            QdrantError::NotImplemented => StatusCode::NOT_IMPLEMENTED,
            QdrantError::Conflict => StatusCode::CONFLICT,
            QdrantError::ConflictWithMessage(_) => StatusCode::CONFLICT,
        };

        metrics::error_metrics("http", status_code.as_str());
        (
            status_code,
            Json(ApiResponse::<()>::error(Instant::now(), self)),
        )
            .into_response()
    }
}

impl From<sqlx::Error> for QdrantError {
    fn from(value: sqlx::Error) -> Self {
        metrics::error_metrics("sqlx", "error");
        error!("SQL ERROR: {}", value);
        if no_such_table(&value) {
            QdrantError::CollectionNotFound
        } else {
            internal_server_error("".into(), value)
        }
    }
}

impl From<model::error::Error> for QdrantError {
    fn from(err: model::error::Error) -> Self {
        metrics::error_metrics("model", "error");

        let msg = match (&err.ctx, &err.parent) {
            (Some(ctx), Some(parent)) => {
                format!("[{}] {}: {:?}", ctx.id(), err.message, parent)
            }
            (Some(ctx), None) => {
                format!("[{}] {}", ctx.id(), err.message)
            }
            (None, Some(parent)) => {
                format!("{}: {:?}", err.message, parent)
            }
            (None, None) => err.message.clone(),
        };

        let trace_id = err
            .ctx
            .map(|ctx| format!("[trace-id:{}]", ctx.id()))
            .unwrap_or_default();

        match err.kind {
            model::error::ErrorKind::NotFound => {
                warn!("ModelError: {}", msg);
                QdrantError::CollectionNotFoundWithTraceId(trace_id)
            }
            model::error::ErrorKind::BadRequest => {
                warn!("ModelError: {}", msg);
                QdrantError::BadRequestWithMsg(format!("{} {}", trace_id, err.message))
            }
            model::error::ErrorKind::Unauthorized => {
                warn!("ModelError: {}", msg);
                QdrantError::Unauthorized
            }
            model::error::ErrorKind::Conflict => {
                warn!("ModelError: {}", msg);
                QdrantError::Conflict
            }
            _ => {
                error!("ModelError: {}", msg);
                QdrantError::InternalServerErrorWithMsg(format!("{} {}", trace_id, err.message))
            }
        }
    }
}

impl From<MutexError> for QdrantError {
    fn from(value: MutexError) -> Self {
        metrics::error_metrics("mutex", "error");

        let msg = match &value.ctx {
            Some(ctx) => format!("[{}] {}", ctx.id(), value.message),
            None => value.message,
        };

        if let Some(ctx) = value.ctx {
            ctx.push_phase("mutex_error", Some(msg.clone()));
        }

        warn!("MutexError: {}", msg);
        QdrantError::ConflictWithMessage(msg)
    }
}

impl From<serde_json::Error> for QdrantError {
    fn from(value: serde_json::Error) -> Self {
        metrics::error_metrics("serde_json", "error");

        error!("serde_json::Error: {}", value);
        internal_server_error("".into(), value)
    }
}

impl From<anyhow::Error> for QdrantError {
    fn from(value: Error) -> Self {
        metrics::error_metrics("anyhow", "error");

        error!("anyhow::Error: {}", value);
        internal_server_error("".into(), value)
    }
}

impl From<axum::extract::multipart::MultipartError> for QdrantError {
    fn from(value: axum::extract::multipart::MultipartError) -> Self {
        metrics::error_metrics("multipart", "error");

        warn!("MultipartError: {}", value);
        QdrantError::BadRequestWithMsg(value.to_string())
    }
}

impl From<reqwest::Error> for QdrantError {
    fn from(value: reqwest::Error) -> Self {
        metrics::error_metrics("reqwest", "error");

        error!("reqwest::Error: {}", value);
        QdrantError::InternalServerErrorWithMsg(value.to_string())
    }
}

impl From<fts_index::error::Error> for QdrantError {
    fn from(value: fts_index::error::Error) -> Self {
        metrics::error_metrics("fts_index", "error");

        error!("fts_index::Error: {}", value);
        QdrantError::InternalServerErrorWithMsg(value.to_string())
    }
}
