use serde::{Deserialize, Serialize};
use std::fmt;

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

#[derive(Debug, Deserialize, Serialize)]
pub struct Error {
  pub status: u16,
  pub details: String,
}

impl Error {
  pub fn new(status: u16, details: String) -> Self {
    Self { status, details }
  }

  pub fn not_found(details: String) -> Self {
    Self::new(404, details)
  }

  pub fn internal_server_error(details: String) -> Self {
    Self::new(500, details)
  }
}

impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    f.write_str(self.details.as_str())
  }
}

impl std::error::Error for Error {
  fn description(&self) -> &str {
    &self.details
  }
}

impl From<std::io::Error> for Error {
  fn from(error: std::io::Error) -> Self {
    Self::new(500, format!("IO error: {}", error))
  }
}

impl From<std::string::FromUtf8Error> for Error {
  fn from(error: std::string::FromUtf8Error) -> Self {
    Self::new(500, format!("UTF-8 error: {}", error))
  }
}

impl From<std::env::VarError> for Error {
  fn from(error: std::env::VarError) -> Self {
    Self::new(500, format!("Environment variable error: {}", error))
  }
}

impl From<sqlx::Error> for Error {
  fn from(error: sqlx::Error) -> Self {
    match error {
      sqlx::Error::RowNotFound => Error::new(404, "Not found".to_string()),
      sqlx::Error::ColumnIndexOutOfBounds { .. } => Error::new(422, error.to_string()),
      sqlx::Error::ColumnNotFound { .. } => Error::new(422, error.to_string()),
      sqlx::Error::ColumnDecode { .. } => Error::new(422, error.to_string()),
      sqlx::Error::Decode(_) => Error::new(422, error.to_string()),
      sqlx::Error::PoolTimedOut => Error::new(503, error.to_string()),
      sqlx::Error::PoolClosed => Error::new(503, error.to_string()),
      sqlx::Error::Database(err) => {
        if let Some(code) = err.code() {
          if code.trim() == "23503" {
            return Error::new(409, err.to_string());
          }
        }
        Error::new(500, err.to_string())
      }
      _ => Error::new(500, error.to_string()),
    }
  }
}

impl From<sqlx::migrate::MigrateError> for Error {
  fn from(error: sqlx::migrate::MigrateError) -> Self {
    Error::new(500, error.to_string())
  }
}

impl From<redis::RedisError> for Error {
  fn from(error: redis::RedisError) -> Self {
    Self::new(500, format!("Redis error: {}", error))
  }
}

impl From<reqwest::Error> for Error {
  fn from(error: reqwest::Error) -> Self {
    Self::new(500, format!("HTTP client error: {}", error))
  }
}

impl From<serde_json::Error> for Error {
  fn from(error: serde_json::Error) -> Self {
    Self::new(500, format!("JSON error: {}", error))
  }
}

impl From<uuid::Error> for Error {
  fn from(error: uuid::Error) -> Self {
    Self::new(500, format!("UUID error: {}", error))
  }
}
