#include "fixture.h"
#include "test_utils.h"

#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/firmware_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/rollout_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/branch_gateway.h>

#include <maps/infra/yacare/include/test_utils.h>
#include <maps/libs/http/include/test_utils.h>

#include <yandex/maps/proto/firmware_updater/storage/firmware.pb.h>
#include <yandex/maps/proto/firmware_updater/storage/upload.pb.h>

#include <library/cpp/testing/gtest/gtest.h>

namespace proto = yandex::maps::proto::firmware_updater::storage;

namespace maps::fw_updater::storage::tests {

namespace {

const std::string LOGIN = "john";
const std::string HARDWARE_1 = "nanopi-neo4";
const std::string HARDWARE_2 = "raspberry-pi-3b";
const std::string ROOTFS = "rootfs";
const std::string VER_1 = "1.0.0";
const std::string VER_2 = "2.0.0";
const std::string VER_3 = "3.0.0";

const std::string URL_LIST_FIRMWARES = "http://localhost/v2/firmware/list";
const std::string URL_DELETE_FIRMWARE = "http://localhost/v2/firmware/delete";

void addUserRole(pqxx::transaction_base& txn)
{
    idm::addUserRole(txn, LOGIN, idm::parseSlugPath("project/one/subproject/one-A/role/manager"));
    txn.commit();
}

http::MockResponse performFirmwareDeleteRequest(
    const std::string& hardware,
    const std::string& slot,
    const std::string& version)
{
    auto request = http::MockRequest(http::DELETE,
        http::URL(URL_DELETE_FIRMWARE)
            .addParam("hardware", hardware)
            .addParam("slot", slot)
            .addParam("version", version)
        );
    return yacare::performTestRequest(request);
}

} // namespace

TEST_F(Fixture, test_list_firmwares)
{
    auto txn = txnHandle();
    db::Firmwares firmwares {
        {HARDWARE_1, db::Slot::Rootfs, VER_1, "http://s3.yandex.ru/hw1-rootfs-key1", 4312084, "md5-1"},
        {HARDWARE_1, db::Slot::Rootfs, VER_2, "http://s3.yandex.ru/hw1-rootfs-key2", 4312084, "md5-2"},
        {HARDWARE_1, db::Slot::Rootfs, VER_3, "http://s3.yandex.ru/hw1-rootfs-key3", 4312084, "md5-3"},
        {HARDWARE_2, db::Slot::Rootfs, VER_1, "http://s3.yandex.ru/hw2-rootfs-key1", 4312084, "md5-4"}
    };
    db::FirmwareGateway{*txn}.insert(firmwares);
    txn->commit();

    // No userInfo provided
    auto request = http::MockRequest(http::GET,
        http::URL(URL_LIST_FIRMWARES).addParam("hardware", HARDWARE_1));
    auto response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    addUserRole(*txnHandle());

    // No rights in IDM
    request = http::MockRequest(http::GET,
        http::URL(URL_LIST_FIRMWARES).addParam("hardware", HARDWARE_2));
    response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 403);

    // List all
    {
        request = http::MockRequest(http::GET,
            http::URL(URL_LIST_FIRMWARES).addParam("hardware", HARDWARE_1));
        response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 200);
        EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
        proto::FirmwaresList firmwaresList;
        ASSERT_TRUE(firmwaresList.ParseFromString(TString(response.body)));
        ASSERT_EQ(firmwaresList.firmwares_size(), 3);
        for (size_t i = 0; i < 3; ++i) {
            EXPECT_TRUE(areEqual(firmwaresList.firmwares(i), firmwares[2 - i]));
        }
    }

    // List with filter
    {
        request = http::MockRequest(http::GET,
            http::URL(URL_LIST_FIRMWARES)
                .addParam("hardware", HARDWARE_1)
                .addParam("slot", ROOTFS)
            );
        response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 200);
        proto::FirmwaresList firmwaresList;
        ASSERT_TRUE(firmwaresList.ParseFromString(TString(response.body)));
        ASSERT_EQ(firmwaresList.firmwares_size(), 3);
        for (size_t i = 0; i < 3; ++i) {
            EXPECT_TRUE(areEqual(firmwaresList.firmwares(i), firmwares[2 - i]));
        }
    }

    // Unknown hardware
    {
        request = http::MockRequest(http::GET,
            http::URL(URL_LIST_FIRMWARES).addParam("hardware", "unknown-hardware"));
        response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 400);
    }

    // Pagination
    {
        request = http::MockRequest(http::GET,
            http::URL(URL_LIST_FIRMWARES)
                .addParam("hardware", HARDWARE_1)
                .addParam("results", 2)
                .addParam("skip", 0)
            );
        response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 200);
        proto::FirmwaresList pageOneFirmwares;
        ASSERT_TRUE(pageOneFirmwares.ParseFromString(TString(response.body)));
        ASSERT_EQ(pageOneFirmwares.firmwares_size(), 2);
        EXPECT_TRUE(areEqual(pageOneFirmwares.firmwares(0), firmwares[2]));
        EXPECT_TRUE(areEqual(pageOneFirmwares.firmwares(1), firmwares[1]));

        request = http::MockRequest(http::GET,
            http::URL(URL_LIST_FIRMWARES)
                .addParam("hardware", HARDWARE_1)
                .addParam("results", 2)
                .addParam("skip", 2)
            );
        response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 200);
        proto::FirmwaresList pageTwoFirmwares;
        ASSERT_TRUE(pageTwoFirmwares.ParseFromString(TString(response.body)));
        ASSERT_EQ(pageTwoFirmwares.firmwares_size(), 1);
        EXPECT_TRUE(areEqual(pageTwoFirmwares.firmwares(0), firmwares[0]));
    }
}


TEST_F(Fixture, test_delete_firmware_unauthorized)
{
    auto txn = txnHandle();
    db::Firmware firmware{HARDWARE_1, db::Slot::Rootfs, VER_1, "http://s3.yandex.ru/hw1-rootfs-key1", 4312084, "md5-1"};
    db::FirmwareGateway{*txn}.insert(firmware);
    txn->commit();

    // No userInfo provided
    auto response = performFirmwareDeleteRequest(HARDWARE_1, ROOTFS, VER_1);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // No rights in IDM
    response = performFirmwareDeleteRequest(HARDWARE_1, ROOTFS, VER_1);
    EXPECT_EQ(response.status, 403);
}


TEST_F(Fixture, test_delete_firmware_when_rollout_exists)
{
    auto txn = txnHandle();
    db::Firmware firmware{HARDWARE_1, db::Slot::Rootfs, VER_1, "http://s3.yandex.ru/hw1-rootfs-key1", 4312084, "md5-1"};
    db::FirmwareGateway{*txn}.insert(firmware);
    db::Branch branch{"testing"};
    db::BranchGateway{*txn}.insert(branch);
    db::Rollout rollout{branch.name(), firmware.id(), "seed"};
    db::RolloutGateway{*txn}.insert(rollout);
    txn->commit();

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    addUserRole(*txnHandle());

    auto response = performFirmwareDeleteRequest(HARDWARE_1, ROOTFS, VER_1);
    EXPECT_EQ(response.status, 409);
}


TEST_F(Fixture, test_delete_firmware)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    addUserRole(*txnHandle());

    // Upload firmware
    http::MockRequest request(http::PUT,
        http::URL("http://localhost/v2/firmware/upload/start")
            .addParam("hardware", HARDWARE_1)
            .addParam("slot", ROOTFS)
            .addParam("version", VER_1)
        );
    auto response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 201);
    proto::Upload upload;
    Y_PROTOBUF_SUPPRESS_NODISCARD upload.ParseFromString(TString(response.body));

    request = http::MockRequest(http::PUT,
        http::URL("http://localhost/v2/firmware/upload/add_part")
            .addParam("id", upload.id())
            .addParam("part", 1)
        );
    request.body = std::string(5 * 1024 * 1024, 'a');
    response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 200);

    request = http::MockRequest(http::POST,
         http::URL("http://localhost/v2/firmware/upload/finish")
             .addParam("id", upload.id()));
    response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 200);

    // Check firmware is there
    request = http::MockRequest(http::GET,
        http::URL(URL_LIST_FIRMWARES)
            .addParam("hardware", HARDWARE_1)
            .addParam("slot", ROOTFS)
        );

    response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 200);
    proto::FirmwaresList firmwaresList;
    Y_PROTOBUF_SUPPRESS_NODISCARD firmwaresList.ParseFromString(TString(response.body));
    EXPECT_EQ(firmwaresList.firmwares_size(), 1);

    // Delete firmware
    response = performFirmwareDeleteRequest(HARDWARE_1, ROOTFS, VER_1);
    EXPECT_EQ(response.status, 200);

    // Check firmware is not there
    request = http::MockRequest(http::GET,
        http::URL(URL_LIST_FIRMWARES)
            .addParam("hardware", HARDWARE_1)
            .addParam("slot", ROOTFS)
        );

    response = yacare::performTestRequest(request);
    EXPECT_EQ(response.status, 204);
}


} // namespace maps::fw_updater::storage::tests
