use context::Context;
use sqlx::migrate::MigrateError;
use std::{fmt::Display, sync::Arc};

pub type Result<R> = std::result::Result<R, Error>;

#[derive(Debug)]
pub enum ErrorKind {
    Internal,
    Database,
    NotFound,
    BadRequest,
    Conflict,
    Unauthorized,
}

#[derive(Debug)]
pub struct Error {
    pub ctx: Option<Arc<Context>>,
    pub kind: ErrorKind,
    pub message: String,
    pub parent: Option<Box<dyn std::error::Error + Send + Sync>>,
}

impl std::error::Error for Error {}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match (&self.ctx, &self.parent) {
            (Some(ctx), Some(parent)) => write!(f, "[{}] {}: {}", ctx.id(), self.message, parent),
            (Some(ctx), None) => write!(f, "[{}] {}", ctx.id(), self.message),
            (None, Some(parent)) => write!(f, "{}: {}", self.message, parent),
            (None, None) => write!(f, "{}", self.message),
        }
    }
}

impl From<anyhow::Error> for Error {
    fn from(err: anyhow::Error) -> Self {
        Self {
            ctx: None,
            kind: ErrorKind::Internal,
            message: "".to_string(),
            parent: Some(err.into()),
        }
    }
}

impl From<sqlx::Error> for Error {
    fn from(err: sqlx::Error) -> Self {
        let (kind, msg) = if no_such_table(&err) {
            (ErrorKind::NotFound, "collection not found")
        } else if vector_dimensions_error(&err) {
            (ErrorKind::BadRequest, "vectors diamensions does not fit")
        } else if column_not_found(&err) {
            (
                ErrorKind::BadRequest,
                "index not found, make sure all filters field are indexed",
            )
        } else if duplicate_entry(&err) {
            (ErrorKind::Conflict, "duplicate entry")
        } else if row_not_found(&err) {
            (ErrorKind::NotFound, "row not found")
        } else if data_too_long(&err) {
            (ErrorKind::BadRequest, "data too long")
        } else {
            (ErrorKind::Database, "database error")
        };

        Error {
            kind,
            ctx: None,
            message: msg.to_string(),
            parent: Some(Box::new(err)),
        }
    }
}

impl From<MigrateError> for Error {
    fn from(err: MigrateError) -> Self {
        Error {
            ctx: None,
            kind: ErrorKind::Database,
            message: err.to_string(),
            parent: Some(Box::new(err)),
        }
    }
}

impl Error {
    pub fn new(kind: ErrorKind, message: String) -> Self {
        Error {
            ctx: None,
            kind,
            message,
            parent: None,
        }
    }

    pub fn ctx(mut self, ctx: Arc<Context>) -> Self {
        ctx.push_phase("model_error", None);
        self.ctx = Some(ctx);
        self
    }
}

fn is_database_error(err: &sqlx::Error, number: u16, code: &str) -> bool {
    if let Some(db_error) = err.as_database_error() {
        if let Some(mysql_db_error) = db_error.try_downcast_ref::<sqlx::mysql::MySqlDatabaseError>()
        {
            return mysql_db_error.number() == number && mysql_db_error.code() == Some(code);
        }
        return false;
    }
    false
}

pub fn no_such_table(err: &sqlx::Error) -> bool {
    is_database_error(err, 1146, "42S02")
}

// error returned from database: 1105 (HY000): [components/tidb_query_datatype/src/codec/mysql/vector.rs:168]: Vectors must be of the same length
pub fn vector_dimensions_error(err: &sqlx::Error) -> bool {
    err.to_string().contains("dimensions, does not fit")
        || err
            .to_string()
            .contains("Vectors must be of the same length")
        || err
            .to_string()
            .contains("vectors have different dimensions")
        || err.to_string().contains("not match index dimensions")
}

pub fn db_exists(err: &sqlx::Error) -> bool {
    is_database_error(err, 1007, "HY000")
}

pub fn is_bad_db(err: &sqlx::Error) -> bool {
    is_database_error(err, 1049, "42000")
}

pub fn is_table_exists(err: &sqlx::Error) -> bool {
    is_database_error(err, 1050, "42S01")
}

pub fn no_database_selected(err: &sqlx::Error) -> bool {
    is_database_error(err, 1046, "3D000")
}

pub fn column_not_found(err: &sqlx::Error) -> bool {
    is_database_error(err, 1054, "42S22")
}

pub fn duplicate_entry(err: &sqlx::Error) -> bool {
    is_database_error(err, 1062, "23000")
}

pub fn row_not_found(err: &sqlx::Error) -> bool {
    err.to_string()
        .contains("no rows returned by a query that expected to return at least one row")
}

pub fn data_too_long(err: &sqlx::Error) -> bool {
    is_database_error(err, 1406, "22001")
}
