use crate::{
  endpoint::FileEndpoint,
  error::Error,
  list::{TreeItem, TreeItemKind, TreeItems, TreeList, strip_relative_path},
};
use async_trait::async_trait;
use std::{
  convert::TryFrom,
  fs::{DirEntry, Metadata, read_dir},
};

const PATH_SEPARATOR: char = std::path::MAIN_SEPARATOR;

#[async_trait]
impl TreeList for FileEndpoint {
  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 list_result = read_dir(absolute_path).map(|entries| {
      entries
        .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, Result<DirEntry, std::io::Error>)> for TreeItem {
  type Error = Error;

  fn try_from(
    (root_path, dir_entry): (&str, Result<DirEntry, std::io::Error>),
  ) -> Result<Self, Self::Error> {
    let entry = dir_entry?;
    let metadata = entry.metadata()?;
    let path = entry.path();
    let path = path
      .to_str()
      .ok_or_else(|| Error::Other(format!("Cannot get directory entry path: {path:?}")))?;
    let root_path = root_path.trim_end_matches(PATH_SEPARATOR);
    let relative_path = strip_relative_path(root_path, path, PATH_SEPARATOR);

    let size = metadata.len();
    let last_update = metadata.modified()?;
    let kind = TreeItemKind::try_from(metadata)?;

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

impl TryFrom<Metadata> for TreeItemKind {
  type Error = Error;

  fn try_from(metadata: Metadata) -> Result<Self, Self::Error> {
    if metadata.is_dir() {
      Ok(TreeItemKind::Folder)
    } else if metadata.is_file() {
      Ok(TreeItemKind::File)
    } else if metadata.is_symlink() {
      Ok(TreeItemKind::Link)
    } else {
      Err(Error::Other(format!(
        "Invalid tree item type: {:?}",
        metadata.file_type()
      )))
    }
  }
}
