use std::collections::HashMap;

use assert_json_diff::assert_json_eq;
use collab_database::rows::database_row_document_id_from_row_id;
use collab_document::blocks::DocumentData;
use collab_entity::CollabType;
use collab_folder::FolderData;
use nanoid::nanoid;
use serde_json::json;

use event_integration_test::document::document_event::DocumentEventTest;
use event_integration_test::event_builder::EventBuilder;
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_encrypt::decrypt_text;
use flowy_server::supabase::define::{USER_DEVICE_ID, USER_EMAIL, USER_UUID};
use flowy_user::entities::{
  AuthenticatorPB, OauthSignInPB, UpdateUserProfilePayloadPB, UserProfilePB,
};
use flowy_user::errors::ErrorCode;
use flowy_user::event_map::UserEvent::*;

use crate::util::*;

#[tokio::test]
async fn third_party_sign_up_test() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new().await;
    let mut map = HashMap::new();
    map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
    map.insert(
      USER_EMAIL.to_string(),
      format!("{}@appflowy.io", nanoid!(6)),
    );
    map.insert(USER_DEVICE_ID.to_string(), uuid::Uuid::new_v4().to_string());
    let payload = OauthSignInPB {
      map,
      authenticator: AuthenticatorPB::Supabase,
    };

    let response = EventBuilder::new(test.clone())
      .event(OauthSignIn)
      .payload(payload)
      .async_send()
      .await
      .parse::<UserProfilePB>();
    dbg!(&response);
  }
}

#[tokio::test]
async fn third_party_sign_up_with_encrypt_test() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new().await;
    test.supabase_party_sign_up().await;
    let user_profile = test.get_user_profile().await.unwrap();
    assert!(user_profile.encryption_sign.is_empty());

    let secret = test.enable_encryption().await;
    let user_profile = test.get_user_profile().await.unwrap();
    assert!(!user_profile.encryption_sign.is_empty());

    let decryption_sign = decrypt_text(user_profile.encryption_sign, &secret).unwrap();
    assert_eq!(decryption_sign, user_profile.id.to_string());
  }
}

#[tokio::test]
async fn third_party_sign_up_with_duplicated_uuid() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new().await;
    let email = format!("{}@appflowy.io", nanoid!(6));
    let mut map = HashMap::new();
    map.insert(USER_UUID.to_string(), uuid::Uuid::new_v4().to_string());
    map.insert(USER_EMAIL.to_string(), email.clone());
    map.insert(USER_DEVICE_ID.to_string(), uuid::Uuid::new_v4().to_string());

    let response_1 = EventBuilder::new(test.clone())
      .event(OauthSignIn)
      .payload(OauthSignInPB {
        map: map.clone(),
        authenticator: AuthenticatorPB::Supabase,
      })
      .async_send()
      .await
      .parse::<UserProfilePB>();
    dbg!(&response_1);

    let response_2 = EventBuilder::new(test.clone())
      .event(OauthSignIn)
      .payload(OauthSignInPB {
        map: map.clone(),
        authenticator: AuthenticatorPB::Supabase,
      })
      .async_send()
      .await
      .parse::<UserProfilePB>();
    assert_eq!(response_1, response_2);
  };
}

#[tokio::test]
async fn third_party_sign_up_with_duplicated_email() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new().await;
    let email = format!("{}@appflowy.io", nanoid!(6));
    test
      .supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
      .await
      .unwrap();
    let error = test
      .supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
      .await
      .err()
      .unwrap();
    assert_eq!(error.code, ErrorCode::Conflict);
  };
}

#[tokio::test]
async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new_anon().await;
    let old_views = test
      .folder_manager
      .get_current_workspace_public_views()
      .await
      .unwrap();
    let old_workspace = test.folder_manager.get_current_workspace().await.unwrap();

    let uuid = uuid::Uuid::new_v4().to_string();
    test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
    let new_views = test
      .folder_manager
      .get_current_workspace_public_views()
      .await
      .unwrap();
    let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();

    assert_eq!(old_views.len(), new_views.len());
    assert_eq!(old_workspace.name, new_workspace.name);
    assert_eq!(old_workspace.views.len(), new_workspace.views.len());
    for (index, view) in old_views.iter().enumerate() {
      assert_eq!(view.name, new_views[index].name);
      assert_eq!(view.layout, new_views[index].layout);
      assert_eq!(view.create_time, new_views[index].create_time);
    }
  }
}

#[tokio::test]
async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new_anon().await;
    let uuid = uuid::Uuid::new_v4().to_string();

    let email = format!("{}@appflowy.io", nanoid!(6));
    // The workspace of the guest will be migrated to the new user with given uuid
    let _user_profile = test
      .supabase_sign_up_with_uuid(&uuid, Some(email.clone()))
      .await
      .unwrap();
    let old_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
    let old_cloud_views = test
      .folder_manager
      .get_current_workspace_public_views()
      .await
      .unwrap();
    assert_eq!(old_cloud_views.len(), 1);
    assert_eq!(old_cloud_views.first().unwrap().child_views.len(), 1);

    // sign out and then sign in as a guest
    test.sign_out().await;

    let _sign_up_context = test.sign_up_as_anon().await;
    let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
    test
      .create_view(&new_workspace.id, "new workspace child view".to_string())
      .await;
    let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
    assert_eq!(new_workspace.views.len(), 2);

    // upload to cloud user with given uuid. This time the workspace of the guest will not be merged
    // because the cloud user already has a workspace
    test
      .supabase_sign_up_with_uuid(&uuid, Some(email))
      .await
      .unwrap();
    let new_cloud_workspace = test.folder_manager.get_current_workspace().await.unwrap();
    let new_cloud_views = test
      .folder_manager
      .get_current_workspace_public_views()
      .await
      .unwrap();
    assert_eq!(new_cloud_workspace, old_cloud_workspace);
    assert_eq!(new_cloud_views, old_cloud_views);
  }
}

#[tokio::test]
async fn get_user_profile_test() {
  if let Some(test) = FlowySupabaseTest::new().await {
    let uuid = uuid::Uuid::new_v4().to_string();
    test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();

    let result = test.get_user_profile().await;
    assert!(result.is_ok());
  }
}

#[tokio::test]
async fn update_user_profile_test() {
  if let Some(test) = FlowySupabaseTest::new().await {
    let uuid = uuid::Uuid::new_v4().to_string();
    let profile = test.supabase_sign_up_with_uuid(&uuid, None).await.unwrap();
    test
      .update_user_profile(UpdateUserProfilePayloadPB::new(profile.id).name("lucas"))
      .await;

    let new_profile = test.get_user_profile().await.unwrap();
    assert_eq!(new_profile.name, "lucas")
  }
}

#[tokio::test]
async fn update_user_profile_with_existing_email_test() {
  if let Some(test) = FlowySupabaseTest::new().await {
    let email = format!("{}@appflowy.io", nanoid!(6));
    let _ = test
      .supabase_sign_up_with_uuid(&uuid::Uuid::new_v4().to_string(), Some(email.clone()))
      .await;

    let profile = test
      .supabase_sign_up_with_uuid(
        &uuid::Uuid::new_v4().to_string(),
        Some(format!("{}@appflowy.io", nanoid!(6))),
      )
      .await
      .unwrap();
    let error = test
      .update_user_profile(
        UpdateUserProfilePayloadPB::new(profile.id)
          .name("lucas")
          .email(&email),
      )
      .await
      .unwrap();
    assert_eq!(error.code, ErrorCode::Conflict);
  }
}

#[tokio::test]
async fn migrate_anon_document_on_cloud_signup() {
  if get_supabase_config().is_some() {
    let test = EventIntegrationTest::new().await;
    let user_profile = test.sign_up_as_anon().await.user_profile;

    let view = test
      .create_view(&user_profile.workspace_id, "My first view".to_string())
      .await;
    let document_event = DocumentEventTest::new_with_core(test.clone());
    let block_id = document_event
      .insert_index(&view.id, "hello world", 1, None)
      .await;

    let _ = test.supabase_party_sign_up().await;

    let workspace_id = test.user_manager.workspace_id().unwrap();
    // After sign up, the documents should be migrated to the cloud
    // So, we can get the document data from the cloud
    let data: DocumentData = test
      .document_manager
      .get_cloud_service()
      .get_document_data(&view.id, &workspace_id)
      .await
      .unwrap()
      .unwrap();
    let block = data.blocks.get(&block_id).unwrap();
    assert_json_eq!(
      block.data,
      json!({
        "delta": [
          {
            "insert": "hello world"
          }
        ]
      })
    );
  }
}

#[tokio::test]
async fn migrate_anon_data_on_cloud_signup() {
  if get_supabase_config().is_some() {
    let (cleaner, user_db_path) = unzip(
      "./tests/user/supabase_test/history_user_db",
      "workspace_sync",
    )
    .unwrap();
    let test =
      EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;
    let user_profile = test.supabase_party_sign_up().await;

    // Get the folder data from remote
    let folder_data: FolderData = test
      .folder_manager
      .get_cloud_service()
      .get_folder_data(&user_profile.workspace_id, &user_profile.id)
      .await
      .unwrap()
      .unwrap();

    let expected_folder_data = expected_workspace_sync_folder_data();
    assert_eq!(folder_data.views.len(), expected_folder_data.views.len());

    // After migration, the ids of the folder_data should be different from the expected_folder_data
    for i in 0..folder_data.views.len() {
      let left_view = &folder_data.views[i];
      let right_view = &expected_folder_data.views[i];
      assert_ne!(left_view.id, right_view.id);
      assert_ne!(left_view.parent_view_id, right_view.parent_view_id);
      assert_eq!(left_view.name, right_view.name);
    }

    assert_ne!(folder_data.workspace.id, expected_folder_data.workspace.id);
    assert_ne!(folder_data.current_view, expected_folder_data.current_view);

    let database_views = folder_data
      .views
      .iter()
      .filter(|view| view.layout.is_database())
      .collect::<Vec<_>>();

    // Try to load the database from the cloud.
    for (i, database_view) in database_views.iter().enumerate() {
      let cloud_service = test.database_manager.get_cloud_service();
      let database_id = test
        .database_manager
        .get_database_id_with_view_id(&database_view.id)
        .await
        .unwrap();
      let editor = test
        .database_manager
        .get_database(&database_id)
        .await
        .unwrap();

      // The database view setting should be loaded by the view id
      let _ = editor
        .get_database_view_setting(&database_view.id)
        .await
        .unwrap();

      let rows = editor.get_rows(&database_view.id).await.unwrap();
      assert_eq!(rows.len(), 3);

      let workspace_id = test.user_manager.workspace_id().unwrap();
      if i == 0 {
        let first_row = rows.first().unwrap().as_ref();
        let icon_url = first_row.meta.icon_url.clone().unwrap();
        assert_eq!(icon_url, "😄");

        let document_id = database_row_document_id_from_row_id(&first_row.row.id);
        let document_data: DocumentData = test
          .document_manager
          .get_cloud_service()
          .get_document_data(&document_id, &workspace_id)
          .await
          .unwrap()
          .unwrap();

        let editor = test
          .document_manager
          .get_document(&document_id)
          .await
          .unwrap();
        let expected_document_data = editor.lock().get_document_data().unwrap();

        // let expected_document_data = test
        //   .document_manager
        //   .get_document_data(&document_id)
        //   .await
        //   .unwrap();
        assert_eq!(document_data, expected_document_data);
        let json = json!(document_data);
        assert_eq!(
          json["blocks"]["LPMpo0Qaab"]["data"]["delta"][0]["insert"],
          json!("Row document")
        );
      }
      assert!(cloud_service
        .get_database_object_doc_state(&database_id, CollabType::Database, &workspace_id)
        .await
        .is_ok());
    }

    drop(cleaner);
  }
}

fn expected_workspace_sync_folder_data() -> FolderData {
  serde_json::from_value::<FolderData>(json!({
    "current_view": "e0811131-9928-4541-a174-20b7553d9e4c",
    "current_workspace_id": "8df7f755-fa5d-480e-9f8e-48ea0fed12b3",
    "views": [
      {
        "children": {
          "items": [
            {
              "id": "e0811131-9928-4541-a174-20b7553d9e4c"
            },
            {
              "id": "53333949-c262-447b-8597-107589697059"
            }
          ]
        },
        "created_at": 1693147093,
        "desc": "",
        "icon": null,
        "id": "e203afb3-de5d-458a-8380-33cd788a756e",
        "is_favorite": false,
        "layout": 0,
        "name": "⭐️ Getting started",
        "parent_view_id": "8df7f755-fa5d-480e-9f8e-48ea0fed12b3"
      },
      {
        "children": {
          "items": [
            {
              "id": "11c697ba-5ed1-41c0-adfc-576db28ad27b"
            },
            {
              "id": "4a5c25e2-a734-440c-973b-4c0e7ab0039c"
            }
          ]
        },
        "created_at": 1693147096,
        "desc": "",
        "icon": null,
        "id": "e0811131-9928-4541-a174-20b7553d9e4c",
        "is_favorite": false,
        "layout": 1,
        "name": "database",
        "parent_view_id": "e203afb3-de5d-458a-8380-33cd788a756e"
      },
      {
        "children": {
          "items": []
        },
        "created_at": 1693147124,
        "desc": "",
        "icon": null,
        "id": "11c697ba-5ed1-41c0-adfc-576db28ad27b",
        "is_favorite": false,
        "layout": 3,
        "name": "calendar",
        "parent_view_id": "e0811131-9928-4541-a174-20b7553d9e4c"
      },
      {
        "children": {
          "items": []
        },
        "created_at": 1693147125,
        "desc": "",
        "icon": null,
        "id": "4a5c25e2-a734-440c-973b-4c0e7ab0039c",
        "is_favorite": false,
        "layout": 2,
        "name": "board",
        "parent_view_id": "e0811131-9928-4541-a174-20b7553d9e4c"
      },
      {
        "children": {
          "items": []
        },
        "created_at": 1693147133,
        "desc": "",
        "icon": null,
        "id": "53333949-c262-447b-8597-107589697059",
        "is_favorite": false,
        "layout": 0,
        "name": "document",
        "parent_view_id": "e203afb3-de5d-458a-8380-33cd788a756e"
      }
    ],
    "workspaces": [
      {
        "child_views": {
          "items": [
            {
              "id": "e203afb3-de5d-458a-8380-33cd788a756e"
            }
          ]
        },
        "created_at": 1693147093,
        "id": "8df7f755-fa5d-480e-9f8e-48ea0fed12b3",
        "name": "Workspace"
      }
    ]
  }))
  .unwrap()
}
