/*
 * 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/update/update_location.h"

#include <memory>
#include <string>

#include <arrow/filesystem/mockfs.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "iceberg/arrow/arrow_io_internal.h"
#include "iceberg/result.h"
#include "iceberg/test/matchers.h"
#include "iceberg/test/update_test_base.h"

namespace iceberg {

class UpdateLocationTest : public UpdateTestBase {};

TEST_F(UpdateLocationTest, SetLocationSuccess) {
  const std::string new_location = "/warehouse/new_location";

  // Create metadata directory for the new location
  auto arrow_fs = std::dynamic_pointer_cast<::arrow::fs::internal::MockFileSystem>(
      static_cast<arrow::ArrowFileSystemFileIO&>(*file_io_).fs());
  ASSERT_TRUE(arrow_fs != nullptr);
  ASSERT_TRUE(arrow_fs->CreateDir(new_location + "/metadata").ok());

  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
  update->SetLocation(new_location);

  ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
  EXPECT_EQ(result, new_location);

  // Commit and verify the location was persisted
  EXPECT_THAT(update->Commit(), IsOk());
  ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
  EXPECT_EQ(reloaded->location(), new_location);
}

TEST_F(UpdateLocationTest, SetLocationMultipleTimes) {
  // Test that setting location multiple times uses the last value
  const std::string final_location = "/warehouse/final_location";

  // Create metadata directory for the new location
  auto arrow_fs = std::dynamic_pointer_cast<::arrow::fs::internal::MockFileSystem>(
      static_cast<arrow::ArrowFileSystemFileIO&>(*file_io_).fs());
  ASSERT_TRUE(arrow_fs != nullptr);
  ASSERT_TRUE(arrow_fs->CreateDir(final_location + "/metadata").ok());

  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
  update->SetLocation("/warehouse/first_location")
      .SetLocation("/warehouse/second_location")
      .SetLocation(final_location);

  ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
  EXPECT_EQ(result, final_location);

  // Commit and verify the final location was persisted
  EXPECT_THAT(update->Commit(), IsOk());
  ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
  EXPECT_EQ(reloaded->location(), final_location);
}

TEST_F(UpdateLocationTest, SetEmptyLocation) {
  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
  update->SetLocation("");

  auto result = update->Apply();
  EXPECT_THAT(result, IsError(ErrorKind::kValidationFailed));
  EXPECT_THAT(result, HasErrorMessage("Location cannot be empty"));
}

TEST_F(UpdateLocationTest, ApplyWithoutSettingLocation) {
  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());

  auto result = update->Apply();
  EXPECT_THAT(result, IsError(ErrorKind::kInvalidArgument));
  EXPECT_THAT(result, HasErrorMessage("Location must be set before applying"));
}

TEST_F(UpdateLocationTest, MultipleUpdatesSequentially) {
  // Get arrow_fs for creating directories
  auto arrow_fs = std::dynamic_pointer_cast<::arrow::fs::internal::MockFileSystem>(
      static_cast<arrow::ArrowFileSystemFileIO&>(*file_io_).fs());
  ASSERT_TRUE(arrow_fs != nullptr);

  // First update
  const std::string first_location = "/warehouse/first";
  ASSERT_TRUE(arrow_fs->CreateDir(first_location + "/metadata").ok());

  ICEBERG_UNWRAP_OR_FAIL(auto update, table_->NewUpdateLocation());
  update->SetLocation(first_location);
  ICEBERG_UNWRAP_OR_FAIL(auto result, update->Apply());
  EXPECT_EQ(result, first_location);
  EXPECT_THAT(update->Commit(), IsOk());

  // Reload and verify
  ICEBERG_UNWRAP_OR_FAIL(auto reloaded, catalog_->LoadTable(table_ident_));
  EXPECT_EQ(reloaded->location(), first_location);

  // Second update
  const std::string second_location = "/warehouse/second";
  ASSERT_TRUE(arrow_fs->CreateDir(second_location + "/metadata").ok());

  ICEBERG_UNWRAP_OR_FAIL(update, reloaded->NewUpdateLocation());
  update->SetLocation(second_location);
  ICEBERG_UNWRAP_OR_FAIL(result, update->Apply());
  EXPECT_EQ(result, second_location);
  EXPECT_THAT(update->Commit(), IsOk());

  // Reload and verify
  ICEBERG_UNWRAP_OR_FAIL(reloaded, catalog_->LoadTable(table_ident_));
  EXPECT_EQ(reloaded->location(), second_location);
}

}  // namespace iceberg
