/*
 * Copyright (C) 2026 UBports Foundation.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "../../src/RcloneItem.h"

#include <gtest/gtest.h>
#include <QJsonObject>
#include <lomiri/storage/common.h>

using namespace std;
using namespace lomiri::storage::provider;
using namespace lomiri::storage::metadata;
using lomiri::storage::ItemType;

namespace
{

constexpr char const* MODTIME = "2019-03-18T20:48:49.202097493+02:00";
constexpr char const* MODTIME_UTC = "2019-03-18T18:48:49.202Z";

QJsonObject file_entry(QString const& name, qint64 size = 12,
                       QString const& modtime = MODTIME)
{
    return QJsonObject{{"Name", name},
                       {"Size", size},
                       {"ModTime", modtime},
                       {"MimeType", "text/plain"},
                       {"IsDir", false}};
}

QJsonObject dir_entry(QString const& name, QString const& modtime = MODTIME)
{
    return QJsonObject{{"Name", name},
                       {"Size", -1},
                       {"ModTime", modtime},
                       {"MimeType", "inode/directory"},
                       {"IsDir", true}};
}

string metadata_string(Item const& item, string const& key)
{
    auto it = item.metadata.find(key);
    return it == item.metadata.end() ? string() : boost::get<string>(it->second);
}

}  // namespace

TEST(StatExists, item_object_is_present)
{
    EXPECT_TRUE(stat_exists(QJsonObject{{"item", file_entry("foo.txt")}}));
    EXPECT_TRUE(stat_exists(QJsonObject{{"item", dir_entry("sub")}}));
}

TEST(StatExists, null_or_missing_is_absent)
{
    EXPECT_FALSE(stat_exists(QJsonObject{{"item", QJsonValue::Null}}));
    EXPECT_FALSE(stat_exists(QJsonObject()));
    EXPECT_FALSE(stat_exists(QJsonObject{{"item", 44}}));
}

TEST(MakeEtag, size_and_utc_modtime)
{
    EXPECT_EQ(string("12,") + MODTIME_UTC, make_etag(file_entry("foo.txt")));
}

// Local timezone doesn't affect the etag
TEST(MakeEtag, ignores_the_reporting_timezone)
{
    string const expected = make_etag(file_entry("foo.txt", 12, MODTIME));
    EXPECT_EQ(expected, make_etag(
        file_entry("foo.txt", 12, "2019-03-18T18:48:49.202097493Z")));
    EXPECT_EQ(expected, make_etag(
        file_entry("foo.txt", 12, "2019-03-18T14:48:49.202097493-04:00")));
}

TEST(MakeEtag, changes_with_size)
{
    EXPECT_NE(make_etag(file_entry("foo.txt", 12)),
              make_etag(file_entry("foo.txt", 13)));
}

TEST(MakeEtag, changes_with_modtime)
{
    EXPECT_NE(make_etag(file_entry("foo.txt")),
              make_etag(file_entry("foo.txt", 12,
                                   "2019-03-18T20:48:50.202097493+02:00")));
}

TEST(MakeEtag, folders_report_no_size)
{
    EXPECT_EQ(string("-1,") + MODTIME_UTC, make_etag(dir_entry("dir")));
}

TEST(MakeEtag, empty_without_a_usable_modtime)
{
    EXPECT_EQ("", make_etag(QJsonObject()));
    EXPECT_EQ("", make_etag(file_entry("foo.txt", 12, "")));
    EXPECT_EQ("", make_etag(file_entry("foo.txt", 12, "yesterday")));
}

TEST(MakeItem, file_from_stat)
{
    Item item = make_item(file_entry("foo.txt"), "foo.txt");

    EXPECT_EQ("foo.txt", item.item_id);
    EXPECT_EQ(vector<string>{"."}, item.parent_ids);
    EXPECT_EQ("foo.txt", item.name);
    EXPECT_TRUE(item.type == ItemType::file);
    EXPECT_EQ(string("12,") + MODTIME_UTC, item.etag);

    EXPECT_EQ(int64_t(12), boost::get<int64_t>(item.metadata.at(SIZE_IN_BYTES)));
    EXPECT_EQ("text/plain", metadata_string(item, CONTENT_TYPE));
    EXPECT_EQ("2019-03-18T18:48:49Z",
              metadata_string(item, LAST_MODIFIED_TIME));
}

TEST(MakeItem, nested_folder_from_stat)
{
    Item item = make_item(dir_entry("b"), "a/b/");

    EXPECT_EQ("a/b/", item.item_id);
    EXPECT_EQ(vector<string>{"a/"}, item.parent_ids);
    EXPECT_EQ("b", item.name);
    EXPECT_TRUE(item.type == ItemType::folder);
    // Unset properties
    EXPECT_FALSE(item.metadata.contains(SIZE_IN_BYTES));
    EXPECT_FALSE(item.metadata.contains(CONTENT_TYPE));
}

TEST(MakeItem, child_id_derived_from_the_parent)
{
    Item item = make_item(file_entry("foo.txt"), ".", true);

    EXPECT_EQ("foo.txt", item.item_id);
    EXPECT_EQ(vector<string>{"."}, item.parent_ids);
    EXPECT_EQ("foo.txt", item.name);
}

TEST(MakeItem, child_folder_id_ends_with_a_slash)
{
    Item item = make_item(dir_entry("sub"), ".", true);

    EXPECT_EQ("sub/", item.item_id);
    EXPECT_TRUE(item.type == ItemType::folder);
}

TEST(MakeItem, child_id_percent_encodes_the_name)
{
    Item item = make_item(file_entry("a b/ü.txt"), "sub/", true);

    EXPECT_EQ("sub/a%20b%2F%C3%BC.txt", item.item_id);
    EXPECT_EQ("a b/ü.txt", item.name);
    EXPECT_EQ(vector<string>{"sub/"}, item.parent_ids);
}

TEST(MakeRootItem, root_item)
{
    Item item = make_root_item(dir_entry(""));

    EXPECT_EQ(".", item.item_id);
    EXPECT_EQ("Root", item.name);
    EXPECT_TRUE(item.type == ItemType::root);
    EXPECT_TRUE(item.parent_ids.empty());
    EXPECT_EQ(string("-1,") + MODTIME_UTC, item.etag);
}

TEST(MakeRootItem, tolerates_an_empty_entry)
{
    Item item = make_root_item(QJsonObject());

    EXPECT_EQ(".", item.item_id);
    EXPECT_EQ("Root", item.name);
    EXPECT_TRUE(item.type == ItemType::root);
    EXPECT_TRUE(item.parent_ids.empty());
    EXPECT_EQ("", item.etag);
    EXPECT_TRUE(item.metadata.empty());
}

int main(int argc, char**argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
