// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2019-2025 Ivan Baidakou

#include "test-utils.h"
#include "access.h"
#include "test_supervisor.h"
#include "diff-builder.h"
#include "model/cluster.h"
#include "fs/scan_scheduler.h"
#include "net/names.h"

using namespace syncspirit;
using namespace syncspirit::test;
using namespace syncspirit::model;

struct fixture_t {
    using target_ptr_t = r::intrusive_ptr_t<fs::scan_scheduler_t>;

    fixture_t() noexcept {
        test::init_logging();
        log = utils::get_logger("fixture");
    }

    void run() noexcept {
        auto my_id =
            device_id_t::from_string("KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD").value();
        my_device = device_t::create(my_id, "my-device").value();

        cluster = new cluster_t(my_device, 1);

        cluster->get_devices().put(my_device);

        r::system_context_t ctx;
        sup = ctx.create_supervisor<supervisor_t>().timeout(timeout).create_registry().finish();
        sup->cluster = cluster;
        sup->configure_callback = [&](r::plugin::plugin_base_t &plugin) {
            plugin.template with_casted<r::plugin::registry_plugin_t>(
                [&](auto &p) { p.register_name(net::names::fs_scanner, sup->get_address()); });
        };

        sup->start();
        sup->do_process();

        folder_id = "1234-5678";

        CHECK(static_cast<r::actor_base_t *>(sup.get())->access<to::state>() == r::state_t::OPERATIONAL);
        sup->do_process();

        target = sup->create_actor<fs::scan_scheduler_t>().timeout(timeout).cluster(cluster).finish();
        sup->do_process();

        REQUIRE(static_cast<r::actor_base_t *>(target.get())->access<to::state>() == r::state_t::OPERATIONAL);
        main();

        sup->do_process();
        sup->shutdown();
        sup->do_process();

        CHECK(static_cast<r::actor_base_t *>(sup.get())->access<to::state>() == r::state_t::SHUT_DOWN);
    }

    virtual void main() noexcept {}

    utils::logger_t log;
    r::pt::time_duration timeout = r::pt::millisec{10};
    r::intrusive_ptr_t<supervisor_t> sup;
    cluster_ptr_t cluster;
    device_ptr_t my_device;
    std::string folder_id;
    target_ptr_t target;
    model::folder_ptr_t folder;
};

void test_1_folder() {
    struct F : fixture_t {
        void main() noexcept override {
            auto db_folder = db::Folder();
            db_folder.set_id(folder_id);

            auto builder = diff_builder_t(*cluster);

            SECTION("zero rescan time => no scan") {
                builder.upsert_folder(db_folder).apply(*sup);
                auto folder = cluster->get_folders().by_id(folder_id);
                CHECK(!folder->is_scanning());
            }

            SECTION("non-zero rescan time") {
                db_folder.set_rescan_interval(3600);
                builder.upsert_folder(db_folder).apply(*sup);

                auto folder = cluster->get_folders().by_id(folder_id);
                CHECK(folder->is_scanning());

                SECTION("scan start/finish") {
                    builder.scan_finish(folder_id).apply(*sup);
                    REQUIRE(!folder->is_scanning());

                    REQUIRE(sup->timers.size() == 1);
                    sup->do_invoke_timer((*sup->timers.begin())->request_id);
                    sup->do_process();
                    REQUIRE(folder->is_scanning());
                }

                SECTION("synchronization start/finish") {
                    builder.scan_finish(folder_id).synchronization_start(folder_id).scan_request(folder_id).apply(*sup);
                    REQUIRE(!folder->is_scanning());
                    REQUIRE(sup->timers.size() == 0);

                    builder.synchronization_finish(folder_id).apply(*sup);
                    REQUIRE(sup->timers.size() == 1);
                    sup->do_invoke_timer((*sup->timers.begin())->request_id);
                    sup->do_process();
                    REQUIRE(folder->is_scanning());
                }
            }

            SECTION("suspending") {
                db_folder.set_rescan_interval(3600);
                builder.upsert_folder(db_folder).apply(*sup);

                auto folder = cluster->get_folders().by_id(folder_id);
                CHECK(folder->is_scanning());
                builder.suspend(*folder).scan_finish(folder_id).apply(*sup);
                REQUIRE(sup->timers.size() == 0);
            }
        }
    };
    F().run();
};

void test_2_folders() {
    struct F : fixture_t {
        void main() noexcept override {
            auto f1_id = "1111";
            auto f2_id = "2222";
            auto db_folder_1 = db::Folder();
            auto db_folder_2 = db::Folder();
            db_folder_1.set_id(f1_id);
            db_folder_2.set_id(f2_id);

            db_folder_1.set_rescan_interval(4000);
            db_folder_2.set_rescan_interval(2000);

            auto builder = diff_builder_t(*cluster);
            builder.upsert_folder(db_folder_1)
                .upsert_folder(db_folder_2)
                .apply(*sup)
                .scan_finish(f2_id)
                .apply(*sup)
                .scan_finish(f1_id)
                .apply(*sup);

            REQUIRE(sup->timers.size() == 1);
            sup->do_invoke_timer((*sup->timers.begin())->request_id);
            sup->do_process();

            auto f1 = cluster->get_folders().by_id(f1_id);
            auto f2 = cluster->get_folders().by_id(f2_id);

            REQUIRE(!f1->is_scanning());
            REQUIRE(f2->is_scanning());

            auto at = r::pt::microsec_clock::local_time() + r::pt::seconds{db_folder_2.rescan_interval() + 1};
            builder.scan_finish(f2_id, at).apply(*sup);

            REQUIRE(sup->timers.size() == 1);
            sup->do_invoke_timer((*sup->timers.begin())->request_id);
            sup->do_process();
            REQUIRE(f1->is_scanning());
            REQUIRE(!f2->is_scanning());

            f2->set_scan_finish({});
            builder.scan_finish(f1_id, at).scan_request(f2_id).apply(*sup);
            REQUIRE(!f1->is_scanning());
            REQUIRE(f2->is_scanning());
        }
    };
    F().run();
};

int _init() {
    REGISTER_TEST_CASE(test_1_folder, "test_1_folder", "[fs]");
    REGISTER_TEST_CASE(test_2_folders, "test_2_folders", "[fs]");
    return 1;
}

static int v = _init();
