/*
 * 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 "iceberg/util/data_file_set.h"

#include <gtest/gtest.h>

#include "iceberg/file_format.h"
#include "iceberg/manifest/manifest_entry.h"
#include "iceberg/row/partition_values.h"

namespace iceberg {

class DataFileSetTest : public ::testing::Test {
 protected:
  std::shared_ptr<DataFile> CreateDataFile(const std::string& path, int64_t size = 100) {
    auto file = std::make_shared<DataFile>();
    file->file_path = path;
    file->file_format = FileFormatType::kParquet;
    file->file_size_in_bytes = size;
    file->record_count = 10;
    file->content = DataFile::Content::kData;
    return file;
  }

  std::shared_ptr<DataFile> CreateDeleteFile(const std::string& path) {
    auto file = CreateDataFile(path);
    file->content = DataFile::Content::kPositionDeletes;
    return file;
  }

  std::shared_ptr<DataFile> CreateDV(const std::string& path, int64_t offset,
                                     int64_t size) {
    auto file = CreateDeleteFile(path);
    file->file_format = FileFormatType::kPuffin;
    file->content_offset = offset;
    file->content_size_in_bytes = size;
    return file;
  }
};

TEST_F(DataFileSetTest, EmptySet) {
  DataFileSet set;
  EXPECT_TRUE(set.empty());
  EXPECT_EQ(set.size(), 0);
  EXPECT_EQ(set.begin(), set.end());
  EXPECT_TRUE(set.as_span().empty());
}

TEST_F(DataFileSetTest, InsertSingleFile) {
  DataFileSet set;
  auto file = CreateDataFile("/path/to/file.parquet");

  auto [iter, inserted] = set.insert(file);
  EXPECT_TRUE(inserted);
  EXPECT_EQ(*iter, file);
  EXPECT_FALSE(set.empty());
  EXPECT_EQ(set.size(), 1);
}

TEST_F(DataFileSetTest, InsertDuplicateFile) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file.parquet");
  auto file2 = CreateDataFile("/path/to/file.parquet");  // Same path

  auto [iter1, inserted1] = set.insert(file1);
  EXPECT_TRUE(inserted1);

  auto [iter2, inserted2] = set.insert(file2);
  EXPECT_FALSE(inserted2);
  EXPECT_EQ(iter1, iter2);   // Should point to the same element
  EXPECT_EQ(set.size(), 1);  // Should still be size 1
}

TEST_F(DataFileSetTest, InsertDifferentFiles) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file1.parquet");
  auto file2 = CreateDataFile("/path/to/file2.parquet");
  auto file3 = CreateDataFile("/path/to/file3.parquet");

  set.insert(file1);
  set.insert(file2);
  set.insert(file3);

  EXPECT_EQ(set.size(), 3);
  EXPECT_FALSE(set.empty());
}

TEST_F(DataFileSetTest, InsertionOrderPreserved) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file1.parquet");
  auto file2 = CreateDataFile("/path/to/file2.parquet");
  auto file3 = CreateDataFile("/path/to/file3.parquet");

  set.insert(file1);
  set.insert(file2);
  set.insert(file3);

  // Iterate and verify order
  std::vector<std::string> paths;
  for (const auto& file : set) {
    paths.push_back(file->file_path);
  }

  EXPECT_EQ(paths.size(), 3);
  EXPECT_EQ(paths[0], "/path/to/file1.parquet");
  EXPECT_EQ(paths[1], "/path/to/file2.parquet");
  EXPECT_EQ(paths[2], "/path/to/file3.parquet");
}

TEST_F(DataFileSetTest, AsSpan) {
  DataFileSet set;
  EXPECT_TRUE(set.as_span().empty());

  // Single element
  auto file0 = CreateDataFile("/path/to/file0.parquet");
  set.insert(file0);
  {
    auto span = set.as_span();
    EXPECT_EQ(span.size(), 1);
    EXPECT_EQ(span[0]->file_path, "/path/to/file0.parquet");
    EXPECT_EQ(span[0], file0);  // Same pointer, span is a view
  }

  // Multiple elements
  auto file1 = CreateDataFile("/path/to/file1.parquet");
  auto file2 = CreateDataFile("/path/to/file2.parquet");
  set.insert(file1);
  set.insert(file2);

  auto span = set.as_span();
  EXPECT_EQ(span.size(), 3);
  EXPECT_EQ(span[0]->file_path, "/path/to/file0.parquet");
  EXPECT_EQ(span[1]->file_path, "/path/to/file1.parquet");
  EXPECT_EQ(span[2]->file_path, "/path/to/file2.parquet");

  // Span matches set iteration order and identity
  size_t i = 0;
  for (const auto& file : set) {
    EXPECT_EQ(span[i], file) << "Span element " << i << " should match set iterator";
    ++i;
  }
  EXPECT_EQ(i, span.size());

  // Span works with range-for
  i = 0;
  for (const auto& file : span) {
    EXPECT_EQ(file->file_path, span[i]->file_path);
    ++i;
  }
  EXPECT_EQ(i, 3);

  set.clear();
  EXPECT_TRUE(set.as_span().empty());
}

TEST_F(DataFileSetTest, InsertDuplicatePreservesOrder) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file1.parquet");
  auto file2 = CreateDataFile("/path/to/file2.parquet");
  auto file3 = CreateDataFile("/path/to/file1.parquet");  // Duplicate of file1

  set.insert(file1);
  set.insert(file2);
  set.insert(file3);  // Should not insert, but order should be preserved

  EXPECT_EQ(set.size(), 2);

  std::vector<std::string> paths;
  for (const auto& file : set) {
    paths.push_back(file->file_path);
  }

  EXPECT_EQ(paths[0], "/path/to/file1.parquet");
  EXPECT_EQ(paths[1], "/path/to/file2.parquet");
}

TEST_F(DataFileSetTest, InsertNullFile) {
  DataFileSet set;
  std::shared_ptr<DataFile> null_file = nullptr;

  auto [iter, inserted] = set.insert(null_file);
  EXPECT_FALSE(inserted);
  EXPECT_EQ(iter, set.end());
  EXPECT_TRUE(set.empty());
  EXPECT_EQ(set.size(), 0);
}

TEST_F(DataFileSetTest, InsertMoveSemantics) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file1.parquet");
  auto file2 = CreateDataFile("/path/to/file2.parquet");

  // Insert using move
  auto [iter1, inserted1] = set.insert(std::move(file1));
  EXPECT_TRUE(inserted1);
  EXPECT_EQ(file1, nullptr);  // Should be moved

  // Insert using copy
  auto [iter2, inserted2] = set.insert(file2);
  EXPECT_TRUE(inserted2);
  EXPECT_NE(file2, nullptr);  // Should still be valid

  EXPECT_EQ(set.size(), 2);
}

TEST_F(DataFileSetTest, Clear) {
  DataFileSet set;
  set.insert(CreateDataFile("/path/to/file1.parquet"));
  set.insert(CreateDataFile("/path/to/file2.parquet"));

  EXPECT_EQ(set.size(), 2);
  set.clear();
  EXPECT_TRUE(set.empty());
  EXPECT_EQ(set.size(), 0);
  EXPECT_EQ(set.begin(), set.end());
}

TEST_F(DataFileSetTest, IteratorOperations) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file1.parquet");
  auto file2 = CreateDataFile("/path/to/file2.parquet");
  auto file3 = CreateDataFile("/path/to/file3.parquet");

  set.insert(file1);
  set.insert(file2);
  set.insert(file3);

  // Test const iterators
  const auto& const_set = set;
  EXPECT_NE(const_set.begin(), const_set.end());
  EXPECT_NE(const_set.cbegin(), const_set.cend());

  // Test iterator increment
  auto it = set.begin();
  EXPECT_EQ((*it)->file_path, "/path/to/file1.parquet");
  ++it;
  EXPECT_EQ((*it)->file_path, "/path/to/file2.parquet");
  ++it;
  EXPECT_EQ((*it)->file_path, "/path/to/file3.parquet");
  ++it;
  EXPECT_EQ(it, set.end());
}

TEST_F(DataFileSetTest, RangeBasedForLoop) {
  DataFileSet set;
  set.insert(CreateDataFile("/path/to/file1.parquet"));
  set.insert(CreateDataFile("/path/to/file2.parquet"));
  set.insert(CreateDataFile("/path/to/file3.parquet"));

  int count = 0;
  for (const auto& file : set) {
    EXPECT_NE(file, nullptr);
    ++count;
  }
  EXPECT_EQ(count, 3);
}

TEST_F(DataFileSetTest, CaseSensitivePaths) {
  DataFileSet set;
  auto file1 = CreateDataFile("/path/to/file.parquet");
  auto file2 = CreateDataFile("/path/to/FILE.parquet");

  set.insert(file1);
  set.insert(file2);

  EXPECT_EQ(set.size(), 2);
}

TEST_F(DataFileSetTest, MultipleInsertsSameFile) {
  DataFileSet set;
  auto file = CreateDataFile("/path/to/file.parquet");

  set.insert(file);
  set.insert(file);
  set.insert(file);

  EXPECT_EQ(set.size(), 1);
}

TEST_F(DataFileSetTest, CopyRebuildsDataFileIndex) {
  DataFileSet original;
  original.insert(CreateDataFile("/path/to/file1.parquet"));
  original.insert(CreateDataFile("/path/to/file2.parquet"));

  DataFileSet copy = original;
  auto duplicate = CreateDataFile("/path/to/file1.parquet");

  auto [iter, inserted] = copy.insert(duplicate);
  EXPECT_FALSE(inserted);
  ASSERT_NE(iter, copy.end());
  EXPECT_EQ((*iter)->file_path, "/path/to/file1.parquet");
  EXPECT_EQ(copy.size(), 2U);
}

TEST_F(DataFileSetTest, DeleteFileSetDeduplicatesByPathForRegularDeletes) {
  DeleteFileSet set;
  auto first = CreateDeleteFile("/path/to/delete.parquet");
  auto duplicate = CreateDeleteFile("/path/to/delete.parquet");

  auto [first_iter, first_inserted] = set.insert(first);
  EXPECT_TRUE(first_inserted);
  EXPECT_EQ(*first_iter, first);

  auto [duplicate_iter, duplicate_inserted] = set.insert(duplicate);
  EXPECT_FALSE(duplicate_inserted);
  EXPECT_EQ(*duplicate_iter, first);
  EXPECT_EQ(set.size(), 1U);
  EXPECT_TRUE(set.contains(*duplicate));
}

TEST_F(DataFileSetTest, DeleteFileSetDistinguishesDeletionVectorContentRanges) {
  DeleteFileSet set;
  auto first = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10);
  auto same_range = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10);
  auto different_offset = CreateDV("/path/to/dv.puffin", /*offset=*/10, /*size=*/10);
  auto different_size = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/20);

  EXPECT_TRUE(set.insert(first).second);
  EXPECT_FALSE(set.insert(same_range).second);
  EXPECT_TRUE(set.insert(different_offset).second);
  EXPECT_TRUE(set.insert(different_size).second);
  EXPECT_EQ(set.size(), 3U);
}

TEST_F(DataFileSetTest, DeleteFileSetCopyRebuildsIndex) {
  DeleteFileSet original;
  original.insert(CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10));
  original.insert(CreateDV("/path/to/dv.puffin", /*offset=*/10, /*size=*/10));

  DeleteFileSet copy = original;
  auto duplicate = CreateDV("/path/to/dv.puffin", /*offset=*/0, /*size=*/10);

  auto [iter, inserted] = copy.insert(duplicate);
  EXPECT_FALSE(inserted);
  ASSERT_NE(iter, copy.end());
  EXPECT_EQ((*iter)->content_offset, 0);
  EXPECT_EQ(copy.size(), 2U);
}

}  // namespace iceberg
