use crate::{
  Error,
  endpoint::OneDriveEndpoint,
  list::{TreeItem, TreeItemKind, TreeItems, TreeList, join_path, strip_relative_path},
};
use chrono::DateTime;
use onedrive_api::{ItemLocation, resource::DriveItem};
use std::{
  convert::{TryFrom, TryInto},
  time::SystemTime,
};

const PATH_SEPARATOR: char = '/';

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

    let item_location = ItemLocation::from_path(&path).ok_or_else(|| {
      Error::OneDrivePath(Self::get_absolute_path(root_path, prefix, PATH_SEPARATOR))
    })?;

    let drive = self.drive();

    let children = drive.list_children(item_location).await?;

    children
      .iter()
      .map(|drive_item| (drive_item, root_path, path.as_str()).try_into())
      .collect()
  }
}

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

  fn try_from(
    (drive_item, root_path, total_path): (&DriveItem, &str, &str),
  ) -> Result<Self, Self::Error> {
    let kind = match (&drive_item.folder, &drive_item.file, &drive_item.location) {
      (Some(_), None, None) => TreeItemKind::Folder,
      (None, Some(_), None) => TreeItemKind::File,
      (None, None, Some(_)) => TreeItemKind::Link,
      _ => return Err(Error::OneDriveItemKind),
    };

    let item_path = drive_item
      .name
      .as_ref()
      .ok_or_else(|| Error::OneDriveItem("name".to_string()))?
      .clone();

    let path = join_path(total_path, &item_path, PATH_SEPARATOR);
    let path = strip_relative_path(root_path, &path, PATH_SEPARATOR);

    let size = drive_item
      .size
      .ok_or_else(|| Error::OneDriveItem("size".to_string()))? as u64;

    let date_time_sting = drive_item
      .last_modified_date_time
      .as_ref()
      .ok_or_else(|| Error::OneDriveItem("last modified date time".to_string()))?;

    let datetime = DateTime::parse_from_rfc3339(date_time_sting)
      .map_err(|error| Error::from((date_time_sting.as_str(), error)))?;

    let last_update = SystemTime::from(datetime);

    Ok(Self {
      kind,
      path,
      root_path: root_path.to_string(),
      size,
      last_update,
      mime_type: None,
      separator: PATH_SEPARATOR,
    })
  }
}
