/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "paimon/core/snapshot.h"

#include "gtest/gtest.h"
#include "paimon/common/utils/string_utils.h"
#include "paimon/fs/local/local_file_system.h"
#include "paimon/result.h"
#include "paimon/snapshot/snapshot_info.h"
#include "paimon/status.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {

class SnapshotTest : public testing::Test {
 public:
    std::string ReplaceAll(const std::string& str) {
        std::string replaced_str = StringUtils::Replace(str, " ", "");
        replaced_str = StringUtils::Replace(replaced_str, "\t", "");
        replaced_str = StringUtils::Replace(replaced_str, "\n", "");
        // logOffsets was removed from snapshot json; normalize legacy fixtures.
        replaced_str = StringUtils::Replace(replaced_str, R"("logOffsets":{},)", "");
        replaced_str = StringUtils::Replace(replaced_str, R"(,"logOffsets":{})", "");
        replaced_str = StringUtils::Replace(replaced_str, R"("logOffsets":{})", "");
        // changelogManifestList is @JsonInclude(NON_NULL) in Java and omitted on serialization;
        // strip the stale null field from checked-in Java fixtures before comparing.
        replaced_str = StringUtils::Replace(replaced_str, R"("changelogManifestList":null,)", "");
        replaced_str = StringUtils::Replace(replaced_str, R"(,"changelogManifestList":null)", "");
        replaced_str = StringUtils::Replace(replaced_str, R"("changelogManifestList":null)", "");
        return replaced_str;
    }
};

TEST_F(SnapshotTest, TestSimple) {
    std::map<std::string, std::string> properties = {{"key1", "value1"}, {"key2", "value2"}};
    Snapshot snapshot(
        /*version=*/5, /*id=*/10, /*schema_id=*/15, /*base_manifest_list=*/"base_manifest_list", 10,
        /*delta_manifest_list=*/"delta_manifest_list", 20,
        /*changelog_manifest_list=*/"changelog_manifest_list", 30,
        /*index_manifest=*/"index_manifest",
        /*commit_user=*/"commit_user_01", /*commit_identifier=*/20,
        /*commit_kind=*/Snapshot::CommitKind::Compact(), /*time_millis=*/1234,
        /*total_record_count=*/35,
        /*delta_record_count=*/40, /*changelog_record_count=*/45, /*watermark=*/50,
        /*statistics=*/"statistic_test", properties, /*next_row_id=*/0);
    ASSERT_EQ(5, snapshot.Version());
    ASSERT_EQ(10, snapshot.Id());
    ASSERT_EQ(15, snapshot.SchemaId());
    ASSERT_EQ("base_manifest_list", snapshot.BaseManifestList());
    ASSERT_EQ(10, snapshot.BaseManifestListSize().value());
    ASSERT_EQ("delta_manifest_list", snapshot.DeltaManifestList());
    ASSERT_EQ(20, snapshot.DeltaManifestListSize().value());
    ASSERT_EQ("changelog_manifest_list", snapshot.ChangelogManifestList().value());
    ASSERT_EQ(30, snapshot.ChangelogManifestListSize().value());
    ASSERT_EQ("index_manifest", snapshot.IndexManifest().value());
    ASSERT_EQ("commit_user_01", snapshot.CommitUser());
    ASSERT_EQ(20, snapshot.CommitIdentifier());
    ASSERT_EQ(Snapshot::CommitKind::Compact(), snapshot.GetCommitKind());
    ASSERT_EQ(1234, snapshot.TimeMillis());
    ASSERT_EQ(35, snapshot.TotalRecordCount());
    ASSERT_EQ(40, snapshot.DeltaRecordCount());
    ASSERT_EQ(45, snapshot.ChangelogRecordCount().value());
    ASSERT_EQ(50, snapshot.Watermark().value());
    ASSERT_EQ("statistic_test", snapshot.Statistics().value());
    ASSERT_EQ(properties, snapshot.Properties().value());
    ASSERT_EQ(0, snapshot.NextRowId().value());
}

TEST_F(SnapshotTest, TestFromPath) {
    std::string data_path =
        paimon::test::GetDataDir() + "/orc/append_09.db/append_09/snapshot/snapshot-1";
    auto fs = std::make_shared<LocalFileSystem>();
    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromPath(fs, data_path));
    ASSERT_EQ(3, snapshot.Version());
    ASSERT_EQ(1, snapshot.Id());
    ASSERT_EQ(0, snapshot.SchemaId());
    ASSERT_EQ("manifest-list-616d1847-a02c-495f-9cca-2c8b7def0fec-0", snapshot.BaseManifestList());
    ASSERT_EQ(std::nullopt, snapshot.BaseManifestListSize());
    ASSERT_EQ("manifest-list-616d1847-a02c-495f-9cca-2c8b7def0fec-1", snapshot.DeltaManifestList());
    ASSERT_EQ(std::nullopt, snapshot.DeltaManifestListSize());
    ASSERT_EQ(std::nullopt, snapshot.ChangelogManifestList());
    ASSERT_EQ(std::nullopt, snapshot.ChangelogManifestListSize());
    ASSERT_EQ(std::nullopt, snapshot.IndexManifest());
    ASSERT_EQ("b02e4322-9c5f-41e1-a560-c0156fdf7b9c", snapshot.CommitUser());
    ASSERT_EQ(9223372036854775807ll, snapshot.CommitIdentifier());
    ASSERT_EQ(Snapshot::CommitKind::Append(), snapshot.GetCommitKind());
    ASSERT_EQ(1721614343270ll, snapshot.TimeMillis());
    ASSERT_EQ(5, snapshot.TotalRecordCount());
    ASSERT_EQ(5, snapshot.DeltaRecordCount());
    ASSERT_EQ(0, snapshot.ChangelogRecordCount().value());
    ASSERT_EQ(std::nullopt, snapshot.Watermark());
    ASSERT_EQ(std::nullopt, snapshot.Statistics());
    ASSERT_EQ(std::nullopt, snapshot.Properties());
    ASSERT_EQ(std::nullopt, snapshot.NextRowId());
}

TEST_F(SnapshotTest, TestJsonizable) {
    std::string json_str = R"({
        "version" : 3,
        "id" : 1,
        "schemaId" : 0,
        "baseManifestList" : "manifest-list-d96fcc30-99e8-4f45-962b-a1157c56f378-0",
        "baseManifestListSize" : 20,
        "deltaManifestList" : "manifest-list-d96fcc30-99e8-4f45-962b-a1157c56f378-1",
        "deltaManifestListSize" : 50,
        "commitUser" : "0e4d92f7-53b0-40d6-a7c0-102bf3801e6a",
        "commitIdentifier" : 9223372036854775807,
        "commitKind" : "OVERWRITE",
        "timeMillis" : 1711692199281,
        "totalRecordCount" : 3,
        "deltaRecordCount" : 3,
        "changelogRecordCount" : 0
    })";

    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromJsonString(json_str));

    Snapshot expected_snapshot(
        /*version=*/3, /*id=*/1, /*schema_id=*/0, /*base_manifest_list=*/
        "manifest-list-d96fcc30-99e8-4f45-962b-a1157c56f378-0", /*base_manifest_list_size=*/20,
        /*delta_manifest_list=*/"manifest-list-d96fcc30-99e8-4f45-962b-a1157c56f378-1",
        /*delta_manifest_list_size=*/50, /*changelog_manifest_list=*/std::nullopt,
        /*changelog_manifest_list_size=*/std::nullopt, /*index_manifest=*/std::nullopt,
        /*commit_user=*/"0e4d92f7-53b0-40d6-a7c0-102bf3801e6a",
        /*commit_identifier=*/9223372036854775807ll,
        /*commit_kind=*/Snapshot::CommitKind::Overwrite(), /*time_millis=*/1711692199281ll,
        /*total_record_count=*/3, /*delta_record_count=*/3, /*changelog_record_count=*/0,
        /*watermark=*/std::nullopt, /*statistics=*/std::nullopt, /*properties=*/std::nullopt,
        /*next_row_id=*/std::nullopt);
    ASSERT_EQ(expected_snapshot, snapshot);

    ASSERT_OK_AND_ASSIGN(std::string new_json_str, snapshot.ToJsonString());
    ASSERT_EQ(ReplaceAll(json_str), ReplaceAll(new_json_str));
}

TEST_F(SnapshotTest, TestSerializeAndDeserialize) {
    auto se_and_de = [&](const std::string& data_path) {
        auto fs = std::make_shared<LocalFileSystem>();
        std::string json_str;
        ASSERT_OK(fs->ReadFile(data_path, &json_str));
        ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromPath(fs, data_path));
        ASSERT_EQ(snapshot, snapshot);
        ASSERT_OK_AND_ASSIGN(std::string se_json_str, snapshot.ToJsonString());
        ASSERT_EQ(ReplaceAll(json_str), ReplaceAll(se_json_str));
    };
    auto se_and_de_from_str = [&](const std::string& json_str) {
        ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromJsonString(json_str));
        ASSERT_EQ(snapshot, snapshot);
        ASSERT_OK_AND_ASSIGN(std::string se_json_str, snapshot.ToJsonString());
        ASSERT_EQ(ReplaceAll(json_str), ReplaceAll(se_json_str));
    };

    {
        // without indexManifest
        std::string data_path =
            paimon::test::GetDataDir() +
            "/orc/pk_table_scan_and_read_dv.db/pk_table_scan_and_read_dv/snapshot/snapshot-1";
        se_and_de(data_path);
    }
    {
        // with indexManifest
        std::string data_path =
            paimon::test::GetDataDir() +
            "/orc/pk_table_scan_and_read_dv.db/pk_table_scan_and_read_dv/snapshot/snapshot-6";
        se_and_de(data_path);
    }
    {
        // with ManifestListSize
        std::string data_path = paimon::test::GetDataDir() +
                                "/orc/append_with_bsi_bitmap_bloomfilter.db/"
                                "append_with_bsi_bitmap_bloomfilter/snapshot/snapshot-1";
        se_and_de(data_path);
    }
    {
        // with properties
        std::string json_str = R"({
          "version" : 3,
          "id" : 10,
          "schemaId" : 2,
          "baseManifestList" : "base-manifest-list-1",
          "baseManifestListSize" : 100,
          "deltaManifestList" : "delta-manifest-list-2",
          "deltaManifestListSize" : 200,
          "commitUser" : "commit-usr-3",
          "commitIdentifier" : 12,
          "commitKind" : "APPEND",
          "timeMillis" : 1749724197266,
          "totalRecordCount" : 1024,
          "deltaRecordCount" : 4096,
          "watermark" : 1749724196266,
          "statistics" : "statistics-4",
          "properties" : {
             "key0" : "value0",
             "key1" : "value1"
          }
        })";
        se_and_de_from_str(json_str);
    }
    {
        // with next_row_id
        std::string json_str = R"({
          "version" : 3,
          "id" : 10,
          "schemaId" : 2,
          "baseManifestList" : "base-manifest-list-1",
          "baseManifestListSize" : 100,
          "deltaManifestList" : "delta-manifest-list-2",
          "deltaManifestListSize" : 200,
          "commitUser" : "commit-usr-3",
          "commitIdentifier" : 12,
          "commitKind" : "APPEND",
          "timeMillis" : 1749724197266,
          "totalRecordCount" : 1024,
          "deltaRecordCount" : 4096,
          "watermark" : 1749724196266,
          "statistics" : "statistics-4",
          "properties" : {
             "key0" : "value0",
             "key1" : "value1"
          },
          "nextRowId" : 0
        })";
        se_and_de_from_str(json_str);
    }
}

TEST_F(SnapshotTest, TestCommitKindAnalyze) {
    // Test constructing a Snapshot with CommitKind::Analyze
    Snapshot snapshot(
        /*version=*/3, /*id=*/20, /*schema_id=*/5,
        /*base_manifest_list=*/"base-manifest-analyze",
        /*base_manifest_list_size=*/100,
        /*delta_manifest_list=*/"delta-manifest-analyze",
        /*delta_manifest_list_size=*/200,
        /*changelog_manifest_list=*/std::nullopt,
        /*changelog_manifest_list_size=*/std::nullopt,
        /*index_manifest=*/std::nullopt,
        /*commit_user=*/"analyze-user",
        /*commit_identifier=*/42,
        /*commit_kind=*/Snapshot::CommitKind::Analyze(),
        /*time_millis=*/1700000000000ll,
        /*total_record_count=*/0,
        /*delta_record_count=*/0,
        /*changelog_record_count=*/0,
        /*watermark=*/std::nullopt,
        /*statistics=*/"test-statistics",
        /*properties=*/std::nullopt,
        /*next_row_id=*/std::nullopt);

    ASSERT_EQ(Snapshot::CommitKind::Analyze(), snapshot.GetCommitKind());
    ASSERT_EQ("ANALYZE", Snapshot::CommitKind::ToString(snapshot.GetCommitKind()));
}

TEST_F(SnapshotTest, TestCommitKindAnalyzeSerializeAndDeserialize) {
    std::string json_str = R"({
        "version" : 3,
        "id" : 20,
        "schemaId" : 5,
        "baseManifestList" : "base-manifest-analyze",
        "baseManifestListSize" : 100,
        "deltaManifestList" : "delta-manifest-analyze",
        "deltaManifestListSize" : 200,
        "commitUser" : "analyze-user",
        "commitIdentifier" : 42,
        "commitKind" : "ANALYZE",
        "timeMillis" : 1700000000000,
        "totalRecordCount" : 0,
        "deltaRecordCount" : 0,
        "changelogRecordCount" : 0,
        "statistics" : "test-statistics"
    })";

    ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromJsonString(json_str));

    // Verify deserialization
    ASSERT_EQ(20, snapshot.Id());
    ASSERT_EQ(5, snapshot.SchemaId());
    ASSERT_EQ(Snapshot::CommitKind::Analyze(), snapshot.GetCommitKind());
    ASSERT_EQ("test-statistics", snapshot.Statistics().value());

    // Verify round-trip serialization
    ASSERT_OK_AND_ASSIGN(std::string serialized, snapshot.ToJsonString());
    ASSERT_EQ(ReplaceAll(json_str), ReplaceAll(serialized));

    // Verify re-deserialization produces equal snapshot
    ASSERT_OK_AND_ASSIGN(Snapshot deserialized, Snapshot::FromJsonString(serialized));
    ASSERT_EQ(snapshot, deserialized);
}

TEST_F(SnapshotTest, TestCommitKindToStringAndFromString) {
    // Verify all CommitKind values round-trip through ToString/FromString
    ASSERT_EQ("APPEND", Snapshot::CommitKind::ToString(Snapshot::CommitKind::Append()));
    ASSERT_EQ("COMPACT", Snapshot::CommitKind::ToString(Snapshot::CommitKind::Compact()));
    ASSERT_EQ("OVERWRITE", Snapshot::CommitKind::ToString(Snapshot::CommitKind::Overwrite()));
    ASSERT_EQ("ANALYZE", Snapshot::CommitKind::ToString(Snapshot::CommitKind::Analyze()));

    ASSERT_EQ(Snapshot::CommitKind::Append(), Snapshot::CommitKind::FromString("APPEND"));
    ASSERT_EQ(Snapshot::CommitKind::Compact(), Snapshot::CommitKind::FromString("COMPACT"));
    ASSERT_EQ(Snapshot::CommitKind::Overwrite(), Snapshot::CommitKind::FromString("OVERWRITE"));
    ASSERT_EQ(Snapshot::CommitKind::Analyze(), Snapshot::CommitKind::FromString("ANALYZE"));

    // Verify equality/inequality
    ASSERT_FALSE(Snapshot::CommitKind::Analyze() == Snapshot::CommitKind::Append());
    ASSERT_FALSE(Snapshot::CommitKind::Analyze() == Snapshot::CommitKind::Compact());
    ASSERT_FALSE(Snapshot::CommitKind::Analyze() == Snapshot::CommitKind::Overwrite());
    ASSERT_TRUE(Snapshot::CommitKind::Analyze() == Snapshot::CommitKind::Analyze());
}

TEST_F(SnapshotTest, TestSnapshotInfoCommitKindToString) {
    ASSERT_EQ("APPEND", SnapshotInfo::CommitKindToString(SnapshotInfo::CommitKind::APPEND));
    ASSERT_EQ("COMPACT", SnapshotInfo::CommitKindToString(SnapshotInfo::CommitKind::COMPACT));
    ASSERT_EQ("OVERWRITE", SnapshotInfo::CommitKindToString(SnapshotInfo::CommitKind::OVERWRITE));
    ASSERT_EQ("ANALYZE", SnapshotInfo::CommitKindToString(SnapshotInfo::CommitKind::ANALYZE));
    ASSERT_EQ("UNKNOWN", SnapshotInfo::CommitKindToString(SnapshotInfo::CommitKind::UNKNOWN));
}

TEST_F(SnapshotTest, TestChangelogManifestListSerialization) {
    // Test with changelog_manifest_list set to a non-null value
    {
        std::string json_str = R"({
            "version" : 3,
            "id" : 1,
            "schemaId" : 0,
            "baseManifestList" : "base-manifest-list",
            "deltaManifestList" : "delta-manifest-list",
            "changelogManifestList" : "changelog-manifest-list",
            "changelogManifestListSize" : 42,
            "commitUser" : "user-01",
            "commitIdentifier" : 100,
            "commitKind" : "APPEND",
            "timeMillis" : 1700000000000,
            "totalRecordCount" : 10,
            "deltaRecordCount" : 5,
            "changelogRecordCount" : 3
        })";

        ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromJsonString(json_str));
        ASSERT_EQ("changelog-manifest-list", snapshot.ChangelogManifestList().value());
        ASSERT_EQ(42, snapshot.ChangelogManifestListSize().value());

        ASSERT_OK_AND_ASSIGN(std::string serialized, snapshot.ToJsonString());
        ASSERT_EQ(ReplaceAll(json_str), ReplaceAll(serialized));

        // Verify round-trip
        ASSERT_OK_AND_ASSIGN(Snapshot deserialized, Snapshot::FromJsonString(serialized));
        ASSERT_EQ(snapshot, deserialized);
    }

    // Test with changelog_manifest_list set to null
    {
        std::string json_str = R"({
            "version" : 3,
            "id" : 2,
            "schemaId" : 0,
            "baseManifestList" : "base-manifest-list",
            "deltaManifestList" : "delta-manifest-list",
            "commitUser" : "user-02",
            "commitIdentifier" : 200,
            "commitKind" : "COMPACT",
            "timeMillis" : 1700000001000,
            "totalRecordCount" : 20,
            "deltaRecordCount" : 10,
            "changelogRecordCount" : 0
        })";

        ASSERT_OK_AND_ASSIGN(Snapshot snapshot, Snapshot::FromJsonString(json_str));
        ASSERT_EQ(std::nullopt, snapshot.ChangelogManifestList());
        ASSERT_EQ(std::nullopt, snapshot.ChangelogManifestListSize());

        ASSERT_OK_AND_ASSIGN(std::string serialized, snapshot.ToJsonString());
        ASSERT_EQ(ReplaceAll(json_str), ReplaceAll(serialized));

        // Verify round-trip
        ASSERT_OK_AND_ASSIGN(Snapshot deserialized, Snapshot::FromJsonString(serialized));
        ASSERT_EQ(snapshot, deserialized);
    }
}

}  // namespace paimon::test
