use crate::{error::Error, secret::FtpSecret};
use ftp::{
  FtpStream,
  openssl::ssl::{SslContext, SslMethod},
  types::FileType,
};
use std::convert::TryFrom;
use std::sync::{Arc, Mutex};

#[derive(Debug, Clone)]
pub struct FtpEndpoint {
  connection: Arc<Mutex<FtpStream>>,
  root_directory: String,
}

impl TryFrom<&FtpSecret> for FtpEndpoint {
  type Error = Error;

  fn try_from(secret: &FtpSecret) -> Result<Self, Error> {
    let connection = Self::connect(secret)?;
    let connection = Arc::new(Mutex::new(connection));
    Ok(Self {
      connection,
      root_directory: secret
        .root_directory
        .clone()
        .unwrap_or_else(|| "/".to_string()),
    })
  }
}

impl FtpEndpoint {
  fn connect(secret: &FtpSecret) -> Result<FtpStream, Error> {
    let mut ftp_stream = FtpStream::connect((secret.hostname(), secret.port()))?;
    if secret.is_secure() {
      let builder = SslContext::builder(SslMethod::tls())?;
      let context = builder.build();
      // Switch to secure mode
      ftp_stream = ftp_stream.into_secure(context)?;
    }

    if let (Some(username), Some(password)) = (secret.username(), secret.password()) {
      ftp_stream.login(username.as_str(), password.as_str())?;
    }

    ftp_stream.transfer_type(FileType::Binary)?;

    Ok(ftp_stream)
  }

  pub fn connection(&self) -> Arc<Mutex<FtpStream>> {
    self.connection.clone()
  }

  pub fn absolute_path(&self, path: &str) -> String {
    format!(
      "{}/{}",
      self.root_directory.trim_end_matches('/'),
      path.trim_start_matches('/')
    )
  }
}

impl Drop for FtpEndpoint {
  fn drop(&mut self) {
    if let Err(_error) = self.connection.lock().unwrap().quit() {
      log::debug!("FTP connection already closed.");
    } else {
      log::debug!("FTP connection closed properly.");
    }
  }
}
