use std::{
  fmt::{Debug, Display, Formatter},
  path::PathBuf,
};

pub enum Error {
  Ftp(ftp::FtpError),
  Gcs(cloud_storage::Error),
  Http(reqwest::Error),
  HttpStatus(reqwest::StatusCode),
  IO(std::io::Error),
  Json(serde_json::Error),
  Onedrive(onedrive_api::Error),
  OneDriveItem(String),
  OneDriveItemKind,
  OneDrivePath(String),
  OneDrivePrefix(String),
  Other(String),
  ParseChrono(String, chrono::ParseError),
  ParseInt(String, std::num::ParseIntError),
  ParseUrl(String, url::ParseError),
  RecvAsync(async_std::channel::RecvError),
  Regex(regex::Error),
  S3(String),
  SendAsync(String),
  SendSync(String),
  Sftp(ssh_transfer::Error),
  Ssl(ftp::openssl::error::ErrorStack),
  Tokio(tokio::task::JoinError),
  Unicode(PathBuf),
}

impl From<ftp::FtpError> for Error {
  fn from(error: ftp::FtpError) -> Self {
    Error::Ftp(error)
  }
}

impl From<cloud_storage::Error> for Error {
  fn from(error: cloud_storage::Error) -> Self {
    Error::Gcs(error)
  }
}

impl From<reqwest::Error> for Error {
  fn from(error: reqwest::Error) -> Self {
    Error::Http(error)
  }
}

impl From<reqwest::StatusCode> for Error {
  fn from(status: reqwest::StatusCode) -> Self {
    Error::HttpStatus(status)
  }
}

impl From<std::io::Error> for Error {
  fn from(error: std::io::Error) -> Self {
    Error::IO(error)
  }
}

impl From<serde_json::Error> for Error {
  fn from(error: serde_json::Error) -> Self {
    Error::Json(error)
  }
}

impl From<(&str, chrono::ParseError)> for Error {
  fn from((string, error): (&str, chrono::ParseError)) -> Self {
    Error::ParseChrono(string.to_string(), error)
  }
}

impl From<(&str, std::num::ParseIntError)> for Error {
  fn from((string, error): (&str, std::num::ParseIntError)) -> Self {
    Error::ParseInt(string.to_string(), error)
  }
}

impl From<(&str, url::ParseError)> for Error {
  fn from((string, error): (&str, url::ParseError)) -> Self {
    Error::ParseUrl(string.to_string(), error)
  }
}

impl From<async_std::channel::RecvError> for Error {
  fn from(error: async_std::channel::RecvError) -> Self {
    Error::RecvAsync(error)
  }
}

impl From<regex::Error> for Error {
  fn from(error: regex::Error) -> Self {
    Error::Regex(error)
  }
}

impl From<aws_smithy_types::date_time::ConversionError> for Error {
  fn from(error: aws_smithy_types::date_time::ConversionError) -> Self {
    Error::S3(format!("{error:?}"))
  }
}

impl<T: Debug> From<aws_sdk_s3::error::SdkError<T>> for Error {
  fn from(error: aws_sdk_s3::error::SdkError<T>) -> Self {
    Error::S3(format!("{error:?}"))
  }
}

impl<T: Debug> From<async_std::channel::SendError<T>> for Error {
  fn from(error: async_std::channel::SendError<T>) -> Self {
    Error::SendAsync(format!(
      "Could not send {:?} message through channel: {error:?}",
      error.0
    ))
  }
}

impl<T: Debug> From<std::sync::mpsc::SendError<T>> for Error {
  fn from(error: std::sync::mpsc::SendError<T>) -> Self {
    Error::SendSync(format!(
      "Could not send {:?} message through channel: {error:?}",
      error.0
    ))
  }
}

impl From<ssh_transfer::Error> for Error {
  fn from(error: ssh_transfer::Error) -> Self {
    Error::Sftp(error)
  }
}

impl From<ftp::openssl::error::ErrorStack> for Error {
  fn from(error: ftp::openssl::error::ErrorStack) -> Self {
    Error::Ssl(error)
  }
}

impl From<tokio::task::JoinError> for Error {
  fn from(error: tokio::task::JoinError) -> Self {
    Error::Tokio(error)
  }
}

impl From<onedrive_api::Error> for Error {
  fn from(error: onedrive_api::Error) -> Self {
    Self::Onedrive(error)
  }
}

impl From<Error> for std::io::Error {
  fn from(error: Error) -> Self {
    match error {
      Error::Ftp(ftp::FtpError::ConnectionError(error)) => error,
      Error::IO(error) => error,
      other => std::io::Error::other(other),
    }
  }
}

impl From<Error> for ftp::FtpError {
  fn from(error: Error) -> Self {
    match error {
      Error::Ftp(error) => error,
      Error::IO(error) => ftp::FtpError::ConnectionError(error),
      other => ftp::FtpError::ConnectionError(std::io::Error::from(other)),
    }
  }
}

impl Display for Error {
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    match self {
      Error::Ftp(error) => write!(f, "{error}"),
      Error::Gcs(error) => write!(f, "{error}"),
      Error::Http(error) => write!(f, "{error}"),
      Error::HttpStatus(status) => write!(f, "Invalid response status: {status}"),
      Error::IO(error) => write!(f, "{error}"),
      Error::Json(error) => write!(f, "{error}"),
      Error::Onedrive(error) => write!(f, "{error:?}"),
      Error::OneDriveItem(error) => write!(f, "OneDrive item has no attribute {error}"),
      Error::OneDriveItemKind => write!(f, "Invalid OneDrive item kind"),
      Error::OneDrivePath(error) => write!(f, "Invalid OneDrive path: {error}"),
      Error::OneDrivePrefix(error) => write!(f, "Invalid OneDrive prefix: {error}"),
      Error::Other(error) => write!(f, "{error}"),
      Error::ParseChrono(string, error) => {
        write!(f, "Unable to parse date time from '{string}': {error}")
      }
      Error::ParseInt(string, error) => {
        write!(f, "Unable to parse '{string}' integer: {error}")
      }
      Error::ParseUrl(url, error) => {
        write!(f, "Unable to parse '{url}' URL: {error}")
      }
      Error::RecvAsync(error) => write!(f, "{error}"),
      Error::Regex(error) => write!(f, "{error}"),
      Error::S3(error) => write!(f, "{error}"),
      Error::SendAsync(error) => write!(f, "{error}"),
      Error::SendSync(error) => write!(f, "{error}"),
      Error::Sftp(error) => write!(f, "{error}"),
      Error::Ssl(error) => write!(f, "{error}"),
      Error::Tokio(error) => write!(f, "{error}"),
      Error::Unicode(error) => write! {f, "Invalid unicode encoding: {error:?}"},
    }
  }
}

impl Debug for Error {
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    match self {
      Error::Ftp(error) => write!(f, "FTP: {error:?}"),
      Error::Gcs(error) => write!(f, "GCS: {error:?}"),
      Error::Http(error) => write!(f, "HTTP: {error:?}"),
      Error::HttpStatus(status) => write!(f, "HTTP: Invalid response status {status:?}"),
      Error::IO(error) => write!(f, "IO: {error:?}"),
      Error::Json(error) => write!(f, "Json: {error:?}"),
      Error::Onedrive(error) => write!(f, "OneDrive: {error:?}"),
      Error::OneDriveItem(error) => write!(f, "OneDrive item has no attribute {error}"),
      Error::OneDriveItemKind => write!(f, "Invalid OneDrive item kind"),
      Error::OneDrivePath(error) => write!(f, "Invalid OneDrive path: {error}"),
      Error::OneDrivePrefix(error) => write!(f, "Invalid OneDrive prefix: {error}"),
      Error::Other(error) => write!(f, "{error:?}"),
      Error::ParseChrono(string, error) => {
        write!(f, "Unable to parse date time from '{string}': {error:?}")
      }
      Error::ParseInt(string, error) => {
        write!(f, "Unable to parse '{string}' integer: {error:?}")
      }
      Error::ParseUrl(url, error) => {
        write!(f, "Unable to parse '{url}' URL: {error:?}")
      }
      Error::RecvAsync(error) => write!(f, "Async channel: {error:?}"),
      Error::Regex(error) => write!(f, "Regex: {error:?}"),
      Error::S3(error) => write!(f, "S3: {error:?}"),
      Error::SendAsync(error) => write!(f, "Async channel: {error}"),
      Error::SendSync(error) => write!(f, "Sync channel: {error}"),
      Error::Sftp(error) => write!(f, "SFTP: {error:?}"),
      Error::Ssl(error) => write!(f, "SSL: {error:?}"),
      Error::Tokio(error) => write!(f, "Tokio: {error:?}"),
      Error::Unicode(error) => write!(f, "Invalid unicode encoding: {error:?}"),
    }
  }
}

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