use crate::{
  endpoint::SftpEndpoint,
  error::Error,
  list::{TreeItem, TreeItemKind, TreeItems, TreeList, strip_relative_path},
};
use chrono::DateTime;
use ssh_transfer::{SftpEntry, SftpEntryKind};
use std::{convert::TryFrom, time::SystemTime};

const PATH_SEPARATOR: char = '/';

#[async_trait::async_trait]
impl TreeList for SftpEndpoint {
  async fn list(&self, root_path: &str, prefix: Option<&str>) -> Result<TreeItems, Error> {
    let absolute_path = Self::get_absolute_path(root_path, prefix, PATH_SEPARATOR);

    let connection = self.connection();

    let list_result = connection
      .lock()
      .unwrap()
      .list_over_sftp(&absolute_path)
      .map(|entries| {
        entries
          .iter()
          .filter_map(|entry| {
            let item = TreeItem::try_from((root_path, entry));

            if let Err(error) = &item {
              log::warn!("Cannot access entry in {root_path}: {error:?}");
            }

            item.ok()
          })
          .collect()
      });

    let items = list_result.unwrap_or_else(|error| {
      log::warn!("Cannot access {root_path} content: {error:?}");
      TreeItems::default()
    });

    Ok(items)
  }
}

impl TryFrom<(&str, &SftpEntry)> for TreeItem {
  type Error = Error;

  fn try_from((root_path, sftp_entry): (&str, &SftpEntry)) -> Result<Self, Self::Error> {
    let path = strip_relative_path(root_path, sftp_entry.path(), PATH_SEPARATOR);
    let kind = TreeItemKind::try_from(sftp_entry.kind())?;
    let size = sftp_entry
      .size()
      .ok_or_else(|| Error::Other("Could not extract entry size".to_string()))?;

    let last_update = sftp_entry
      .last_modification_time()
      .ok_or_else(|| Error::Other("Could not extract entry last modification time".to_string()))
      .map(|timestamp| DateTime::from_timestamp(timestamp as i64, 0))?
      .ok_or_else(|| Error::Other("Invalid last modification time".to_string()))
      .map(SystemTime::from)?;

    Ok(Self::new(
      kind,
      &path,
      root_path,
      size,
      last_update,
      None,
      PATH_SEPARATOR,
    ))
  }
}

impl TryFrom<&SftpEntryKind> for TreeItemKind {
  type Error = Error;

  fn try_from(entry_kind: &SftpEntryKind) -> Result<Self, Self::Error> {
    match entry_kind {
      SftpEntryKind::File => Ok(TreeItemKind::File),
      SftpEntryKind::Directory => Ok(TreeItemKind::Folder),
      SftpEntryKind::SymLink => Ok(TreeItemKind::Link),
      SftpEntryKind::Other => Err(Error::Other("Unsupported entry type".to_string())),
    }
  }
}
