#include <catch_amalgamated.hpp>
#include <cstdint>
#include <fstream>

#include "../client/client.h"
#include "../client/config.h"
#include "../server/server.h"
#include "../util/util.h"
#include "assistant.h"

std::shared_ptr<TransmServer> server;
std::shared_ptr<TransmClient> clientA;
std::shared_ptr<TransmClient> clientB;
std::shared_ptr<ClientConfig> config;

asio::io_context server_context;

constexpr auto ip = "127.0.0.1";
constexpr unsigned short port = 9897;
bool server_suc = false;
constexpr unsigned int max_wait = 3000;
constexpr unsigned int wait_interval = 100;

std::string str_id_a;
int ida_in_b = -1;
std::thread server_th;

std::string test_filea = "filea.dat";
std::string test_fileb = "fileb.dat";
std::string test_sub_dir = "test_sub";
std::string test_task_file = "test_task.txt";

bool test_ls();
bool random_ralated_files();
bool test_up_task(bool encrypt);

void server_run()
{
    server = std::make_shared<TransmServer>(server_context);
    if (!server->start(port)) {
        server_suc = false;
        return;
    }
    server_suc = true;
    server_context.run();
}

bool base_connect()
{
    config = std::make_shared<ClientConfig>();
    if (!config->baseInit()) {
        return false;
    }

    server_th = std::thread(server_run);
    if (value_wait<bool>([]() -> bool { return server_suc; }, true, std::equal_to<bool>(), max_wait,
                         wait_interval) == false) {
        return false;
    }

    clientA = std::make_shared<TransmClient>();
    if (clientA->connect_for_test(ip, std::to_string(port), config->get_config_dir()) == false) {
        return false;
    }

    clientB = std::make_shared<TransmClient>();
    if (clientB->connect_for_test(ip, std::to_string(port), config->get_config_dir()) == false) {
        return false;
    }

    if (value_wait<std::string>([]() -> std::string { return clientA->test_get_own_id(); }, std::string(),
                                std::not_equal_to<std::string>(), max_wait, wait_interval) == false) {
        return false;
    }

    if (value_wait<std::string>([]() -> std::string { return clientB->test_get_own_id(); }, std::string(),
                                std::not_equal_to<std::string>(), max_wait, wait_interval) == false) {
        return false;
    }

    str_id_a = clientA->test_get_own_id();
    std::cout << "clientA id: " << str_id_a << std::endl;
    if (value_wait<int>([]() -> int { return clientB->test_index_by_id(str_id_a); }, -1,
                        std::not_equal_to<int>(), max_wait, wait_interval) == false) {
        return false;
    }
    ida_in_b = clientB->test_index_by_id(str_id_a);
    std::cout << "clientA index In B: " << ida_in_b << std::endl;
    return true;
}

bool main_test()
{
    ON_SCOPE_EXIT
    {
        fc_recovery_color();
    };

    if (!base_connect()) {
        return false;
    }

    std::shared_ptr<int> deleter(new int(), [](int* p) {
        if (clientA) {
            clientA->disconnect_for_test();
        }
        if (clientB) {
            clientB->disconnect_for_test();
        }
        if (server) {
            server->stop();
        }
        server_context.stop();
        if (server_th.joinable()) {
            server_th.join();
        }
        delete p;
    });

    if (!test_ls()) {
        return false;
    }

    if (!random_ralated_files()) {
        return false;
    }

    if (!test_up_task(true)) {
        return false;
    }

    if (!test_up_task(false)) {
        return false;
    }

    std::this_thread::sleep_for(std::chrono::seconds(10));
    return true;
}

// 测试 Ls
bool test_ls()
{
    std::string cmd = std::to_string(ida_in_b) + " .";
    if (!clientB->cmd_ls(cmd)) {
        return false;
    }
    return true;
}

bool test_up_task(bool encrypt)
{
    std::string cmd = std::to_string(ida_in_b) + " " + test_task_file;

    auto fas = test_filea;
    auto fat = test_sub_dir + "/" + test_filea;
    auto fbs = test_fileb;
    auto fbt = test_sub_dir + "/" + test_fileb;

    ON_SCOPE_EXIT
    {
        if (fs::exists(fat)) {
            fs::remove(fat);
        }
        if (fs::exists(fbt)) {
            fs::remove(fbt);
        }
    };

    set_encrypt(encrypt);
    clientB->set_task_state(TransmClient::TaskState::TASK_STATE_IDLE);
    if (!clientB->cmd_sub_task(cmd, true)) {
        return false;
    }

    if (value_wait<TransmClient::TaskState>(
            [&]() -> TransmClient::TaskState { return clientB->get_task_state(); },
            TransmClient::TaskState::TASK_STATE_IDLE, std::not_equal_to<TransmClient::TaskState>(),
            max_wait * 2, wait_interval) == false) {
        return false;
    }

    auto r = clientB->get_task_state();
    if (r != TransmClient::TaskState::TASK_STATE_DONE) {
        return false;
    }

    if (!is_equal_filecontent(fas, fat)) {
        return false;
    }

    if (!is_equal_filecontent(fbs, fbt)) {
        return false;
    }

    std::cout << "****** up task done encrypt:" << encrypt << " ******" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    return true;
}

bool random_ralated_files()
{
    if (!random_file("filea.dat", 1024 * 1024 * 10)) {
        return false;
    }
    if (!random_file("fileb.dat", 1024 * 1024 * 10)) {
        return false;
    }
    if (fs::exists(test_sub_dir)) {
        fs::remove_all(test_sub_dir);
    }
    fs::create_directories(test_sub_dir);
    if (fs::exists(test_task_file)) {
        fs::remove(test_task_file);
    }
    std::ofstream ofs(test_task_file);
    ofs << "${CURRENT}/" << test_filea << "|" << test_sub_dir + "/" << std::endl;
    ofs << test_fileb << "|" << test_sub_dir + "/" << std::endl;
    ofs.close();
    return true;
}

TEST_CASE("transm cmd part", "[cmd]")
{
    SECTION("correctness of cmd")
    {
        REQUIRE(main_test() == true);
    }
}