use crate::entities::view::ViewDataFormatPB;
use crate::entities::{ViewLayoutTypePB, ViewPB};
use crate::services::folder_editor::FolderRevisionMergeable;
use crate::{
  entities::workspace::RepeatedWorkspacePB,
  errors::FlowyResult,
  event_map::{FolderCouldServiceV1, WorkspaceDatabase, WorkspaceUser},
  notification::{send_notification, FolderNotification},
  services::{
    folder_editor::FolderEditor, persistence::FolderPersistence, set_current_workspace,
    AppController, TrashController, ViewController, WorkspaceController,
  },
};
use bytes::Bytes;
use flowy_document::editor::initial_read_me;
use flowy_error::FlowyError;
use flowy_revision::{
  RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket,
};
use folder_model::user_default;
use lazy_static::lazy_static;
use lib_infra::future::FutureResult;

use crate::services::clear_current_workspace;
use crate::services::persistence::rev_sqlite::{
  SQLiteFolderRevisionPersistence, SQLiteFolderRevisionSnapshotPersistence,
};
use flowy_client_sync::client_folder::FolderPad;
use std::convert::TryFrom;
use std::{collections::HashMap, fmt::Formatter, sync::Arc};
use tokio::sync::RwLock as TokioRwLock;
use ws_model::ws_revision::ServerRevisionWSData;
lazy_static! {
  static ref INIT_FOLDER_FLAG: TokioRwLock<HashMap<String, bool>> =
    TokioRwLock::new(HashMap::new());
}
const FOLDER_ID: &str = "folder";
const FOLDER_ID_SPLIT: &str = ":";
#[derive(Clone)]
pub struct FolderId(String);
impl FolderId {
  pub fn new(user_id: &str) -> Self {
    Self(format!("{}{}{}", user_id, FOLDER_ID_SPLIT, FOLDER_ID))
  }
}

impl std::fmt::Display for FolderId {
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    f.write_str(FOLDER_ID)
  }
}

impl std::fmt::Debug for FolderId {
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    f.write_str(FOLDER_ID)
  }
}

impl AsRef<str> for FolderId {
  fn as_ref(&self) -> &str {
    &self.0
  }
}

pub struct FolderManager {
  pub user: Arc<dyn WorkspaceUser>,
  pub(crate) persistence: Arc<FolderPersistence>,
  pub(crate) workspace_controller: Arc<WorkspaceController>,
  pub(crate) app_controller: Arc<AppController>,
  pub(crate) view_controller: Arc<ViewController>,
  pub(crate) trash_controller: Arc<TrashController>,
  web_socket: Arc<dyn RevisionWebSocket>,
  pub(crate) folder_editor: Arc<TokioRwLock<Option<Arc<FolderEditor>>>>,
}

impl FolderManager {
  pub async fn new(
    user: Arc<dyn WorkspaceUser>,
    cloud_service: Arc<dyn FolderCouldServiceV1>,
    database: Arc<dyn WorkspaceDatabase>,
    data_processors: ViewDataProcessorMap,
    web_socket: Arc<dyn RevisionWebSocket>,
  ) -> Self {
    if let Ok(user_id) = user.user_id() {
      // Reset the flag if the folder manager gets initialized, otherwise,
      // the folder_editor will not be initialized after flutter hot reload.
      INIT_FOLDER_FLAG
        .write()
        .await
        .insert(user_id.to_owned(), false);
    }

    let folder_editor = Arc::new(TokioRwLock::new(None));
    let persistence = Arc::new(FolderPersistence::new(
      database.clone(),
      folder_editor.clone(),
    ));

    let trash_controller = Arc::new(TrashController::new(
      persistence.clone(),
      cloud_service.clone(),
      user.clone(),
    ));

    let view_controller = Arc::new(ViewController::new(
      user.clone(),
      persistence.clone(),
      cloud_service.clone(),
      trash_controller.clone(),
      data_processors,
    ));

    let app_controller = Arc::new(AppController::new(
      user.clone(),
      persistence.clone(),
      trash_controller.clone(),
      cloud_service.clone(),
    ));

    let workspace_controller = Arc::new(WorkspaceController::new(
      user.clone(),
      persistence.clone(),
      trash_controller.clone(),
      cloud_service.clone(),
    ));

    Self {
      user,
      persistence,
      workspace_controller,
      app_controller,
      view_controller,
      trash_controller,
      web_socket,
      folder_editor,
    }
  }

  // pub fn network_state_changed(&self, new_type: NetworkType) {
  //     match new_type {
  //         NetworkType::UnknownNetworkType => {},
  //         NetworkType::Wifi => {},
  //         NetworkType::Cell => {},
  //         NetworkType::Ethernet => {},
  //     }
  // }

  pub async fn did_receive_ws_data(&self, data: Bytes) {
    let result = ServerRevisionWSData::try_from(data);
    match result {
      Ok(data) => match self.folder_editor.read().await.clone() {
        None => {},
        Some(editor) => match editor.receive_ws_data(data).await {
          Ok(_) => {},
          Err(e) => tracing::error!("Folder receive data error: {:?}", e),
        },
      },
      Err(e) => {
        tracing::error!("Folder ws data parser failed: {:?}", e);
      },
    }
  }

  /// Called immediately after the application launched with the user sign in/sign up.
  #[tracing::instrument(level = "trace", skip(self), err)]
  pub async fn initialize(&self, user_id: &str, token: &str) -> FlowyResult<()> {
    let mut write_guard = INIT_FOLDER_FLAG.write().await;
    if let Some(is_init) = write_guard.get(user_id) {
      if *is_init {
        return Ok(());
      }
    }
    tracing::debug!("Initialize folder editor");
    let folder_id = FolderId::new(user_id);
    self.persistence.initialize(user_id, &folder_id).await?;

    let pool = self.persistence.db_pool()?;
    let object_id = folder_id.as_ref();
    let disk_cache = SQLiteFolderRevisionPersistence::new(user_id, pool.clone());
    let configuration = RevisionPersistenceConfiguration::new(200, false);
    let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache, configuration);
    let rev_compactor = FolderRevisionMergeable();

    const FOLDER_SP_PREFIX: &str = "folder";
    let snapshot_object_id = format!("{}:{}", FOLDER_SP_PREFIX, object_id);
    let snapshot_persistence =
      SQLiteFolderRevisionSnapshotPersistence::new(&snapshot_object_id, pool);
    let rev_manager = RevisionManager::new(
      user_id,
      folder_id.as_ref(),
      rev_persistence,
      rev_compactor,
      snapshot_persistence,
    );

    let folder_editor = FolderEditor::new(
      user_id,
      &folder_id,
      token,
      rev_manager,
      self.web_socket.clone(),
    )
    .await?;
    *self.folder_editor.write().await = Some(Arc::new(folder_editor));

    self.app_controller.initialize()?;
    self.view_controller.initialize()?;
    write_guard.insert(user_id.to_owned(), true);
    Ok(())
  }

  pub async fn initialize_with_new_user(
    &self,
    user_id: &str,
    token: &str,
    view_data_format: ViewDataFormatPB,
  ) -> FlowyResult<()> {
    DefaultFolderBuilder::build(
      token,
      user_id,
      self.persistence.clone(),
      self.view_controller.clone(),
      || (view_data_format.clone(), Bytes::from(initial_read_me())),
    )
    .await?;
    self.initialize(user_id, token).await
  }

  /// Called when the current user logout
  ///
  pub async fn clear(&self, user_id: &str) {
    self.view_controller.clear_latest_view();
    clear_current_workspace(user_id);
    *self.folder_editor.write().await = None;
  }
}

struct DefaultFolderBuilder();
impl DefaultFolderBuilder {
  async fn build<F: Fn() -> (ViewDataFormatPB, Bytes)>(
    token: &str,
    user_id: &str,
    persistence: Arc<FolderPersistence>,
    view_controller: Arc<ViewController>,
    create_view_fn: F,
  ) -> FlowyResult<()> {
    let workspace_rev = user_default::create_default_workspace();
    tracing::debug!(
      "Create user:{} default workspace:{}",
      user_id,
      workspace_rev.id
    );
    set_current_workspace(user_id, &workspace_rev.id);
    for app in workspace_rev.apps.iter() {
      for (index, view) in app.belongings.iter().enumerate() {
        let (view_data_type, view_data) = create_view_fn();
        if index == 0 {
          let _ = view_controller.set_latest_view(&view.id);
          let layout_type = ViewLayoutTypePB::from(view.layout.clone());
          view_controller
            .create_view(&view.id, &view.name, view_data_type, layout_type, view_data)
            .await?;
        }
      }
    }
    let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?;
    let folder_id = FolderId::new(user_id);
    persistence.save_folder(user_id, &folder_id, folder).await?;
    let repeated_workspace = RepeatedWorkspacePB {
      items: vec![workspace_rev.into()],
    };
    send_notification(token, FolderNotification::DidCreateWorkspace)
      .payload(repeated_workspace)
      .send();
    Ok(())
  }
}

pub trait ViewDataProcessor {
  /// Closes the view and releases the resources that this view has in
  /// the backend
  fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;

  /// Gets the data of the this view.
  /// For example, the data can be used to duplicate the view.
  fn get_view_data(&self, view: &ViewPB) -> FutureResult<Bytes, FlowyError>;

  /// Create a view with the pre-defined data.
  /// For example, the initial data of the grid/calendar/kanban board when
  /// you create a new view.
  fn create_view_with_build_in_data(
    &self,
    user_id: &str,
    view_id: &str,
    name: &str,
    layout: ViewLayoutTypePB,
    data_format: ViewDataFormatPB,
    ext: HashMap<String, String>,
  ) -> FutureResult<(), FlowyError>;

  /// Create a view with custom data
  fn create_view_with_custom_data(
    &self,
    user_id: &str,
    view_id: &str,
    name: &str,
    data: Vec<u8>,
    layout: ViewLayoutTypePB,
    ext: HashMap<String, String>,
  ) -> FutureResult<(), FlowyError>;

  fn data_types(&self) -> Vec<ViewDataFormatPB>;
}

pub type ViewDataProcessorMap =
  Arc<HashMap<ViewDataFormatPB, Arc<dyn ViewDataProcessor + Send + Sync>>>;
