/*
 * 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.
 */

#pragma once

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "iceberg/catalog/rest/endpoint.h"
#include "iceberg/catalog/rest/iceberg_rest_export.h"
#include "iceberg/result.h"
#include "iceberg/table_identifier.h"
#include "iceberg/type_fwd.h"
#include "iceberg/util/macros.h"

/// \file iceberg/catalog/rest/types.h
/// Request and response types for Iceberg REST Catalog API.

namespace iceberg::rest {

/// \brief Status of a REST server-side scan planning operation.
enum class PlanStatus {
  kSubmitted,
  kCompleted,
  kCancelled,
  kFailed,
};

ICEBERG_REST_EXPORT std::string_view ToString(PlanStatus status);
ICEBERG_REST_EXPORT Result<PlanStatus> PlanStatusFromString(std::string_view status_str);

/// \brief Server-provided configuration for the catalog.
struct ICEBERG_REST_EXPORT CatalogConfig {
  std::unordered_map<std::string, std::string> defaults;   // required
  std::unordered_map<std::string, std::string> overrides;  // required
  std::vector<Endpoint> endpoints;

  /// \brief Validates the CatalogConfig.
  Status Validate() const { return {}; }

  bool operator==(const CatalogConfig&) const = default;
};

/// \brief JSON error payload returned in a response with further details on the error.
struct ICEBERG_REST_EXPORT ErrorResponse {
  uint32_t code;        // required
  std::string type;     // required
  std::string message;  // required
  std::vector<std::string> stack;

  /// \brief Validates the ErrorResponse.
  Status Validate() const {
    if (message.empty() || type.empty()) {
      return ValidationFailed("Invalid error response: missing required fields");
    }

    if (code < 400 || code > 600) {
      return ValidationFailed(
          "Invalid error response: code {} is out of range [400, 600]", code);
    }

    // stack is optional, no validation needed
    return {};
  }

  bool operator==(const ErrorResponse&) const = default;
};

/// \brief Request to create a namespace.
struct ICEBERG_REST_EXPORT CreateNamespaceRequest {
  Namespace namespace_;  // required
  std::unordered_map<std::string, std::string> properties;

  /// \brief Validates the CreateNamespaceRequest.
  Status Validate() const { return {}; }

  bool operator==(const CreateNamespaceRequest&) const = default;
};

/// \brief Update or delete namespace properties request.
struct ICEBERG_REST_EXPORT UpdateNamespacePropertiesRequest {
  std::vector<std::string> removals;
  std::unordered_map<std::string, std::string> updates;

  /// \brief Validates the UpdateNamespacePropertiesRequest.
  Status Validate() const {
    for (const auto& key : removals) {
      if (updates.contains(key)) {
        return ValidationFailed("Duplicate key to update and remove: {}", key);
      }
    }
    return {};
  }

  bool operator==(const UpdateNamespacePropertiesRequest&) const = default;
};

/// \brief Request to register a table.
struct ICEBERG_REST_EXPORT RegisterTableRequest {
  std::string name;               // required
  std::string metadata_location;  // required
  bool overwrite = false;

  /// \brief Validates the RegisterTableRequest.
  Status Validate() const {
    if (name.empty()) {
      return ValidationFailed("Missing table name");
    }

    if (metadata_location.empty()) {
      return ValidationFailed("Empty metadata location");
    }

    return {};
  }

  bool operator==(const RegisterTableRequest&) const = default;
};

/// \brief Request to rename a table.
struct ICEBERG_REST_EXPORT RenameTableRequest {
  TableIdentifier source;       // required
  TableIdentifier destination;  // required

  /// \brief Validates the RenameTableRequest.
  Status Validate() const {
    ICEBERG_RETURN_UNEXPECTED(source.Validate());
    ICEBERG_RETURN_UNEXPECTED(destination.Validate());
    return {};
  }

  bool operator==(const RenameTableRequest&) const = default;
};

/// \brief Request to create a table.
struct ICEBERG_REST_EXPORT CreateTableRequest {
  std::string name;  // required
  std::string location;
  std::shared_ptr<Schema> schema;  // required
  std::shared_ptr<PartitionSpec> partition_spec;
  std::shared_ptr<SortOrder> write_order;
  bool stage_create = false;
  std::unordered_map<std::string, std::string> properties;

  /// \brief Validates the CreateTableRequest.
  Status Validate() const {
    if (name.empty()) {
      return ValidationFailed("Missing table name");
    }
    if (!schema) {
      return ValidationFailed("Missing schema");
    }
    return {};
  }

  bool operator==(const CreateTableRequest& other) const;
};

/// \brief An opaque token that allows clients to make use of pagination for list APIs.
using PageToken = std::string;

/// \brief Result body for table create/load/register APIs.
struct ICEBERG_REST_EXPORT LoadTableResult {
  std::string metadata_location;
  std::shared_ptr<TableMetadata> metadata;  // required
  std::unordered_map<std::string, std::string> config;
  // TODO(Li Feiyang): Add std::shared_ptr<StorageCredential> storage_credential;

  /// \brief Validates the LoadTableResult.
  Status Validate() const {
    if (!metadata) {
      return ValidationFailed("Invalid metadata: null");
    }
    return {};
  }

  bool operator==(const LoadTableResult& other) const;
};

/// \brief Alias of LoadTableResult used as the body of CreateTableResponse
using CreateTableResponse = LoadTableResult;

/// \brief Alias of LoadTableResult used as the body of LoadTableResponse
using LoadTableResponse = LoadTableResult;

/// \brief Response body for listing namespaces.
struct ICEBERG_REST_EXPORT ListNamespacesResponse {
  PageToken next_page_token;
  std::vector<Namespace> namespaces;

  /// \brief Validates the ListNamespacesResponse.
  Status Validate() const { return {}; }

  bool operator==(const ListNamespacesResponse&) const = default;
};

/// \brief Response body after creating a namespace.
struct ICEBERG_REST_EXPORT CreateNamespaceResponse {
  Namespace namespace_;  // required
  std::unordered_map<std::string, std::string> properties;

  /// \brief Validates the CreateNamespaceResponse.
  Status Validate() const { return {}; }

  bool operator==(const CreateNamespaceResponse&) const = default;
};

/// \brief Response body for loading namespace properties.
struct ICEBERG_REST_EXPORT GetNamespaceResponse {
  Namespace namespace_;  // required
  std::unordered_map<std::string, std::string> properties;

  /// \brief Validates the GetNamespaceResponse.
  Status Validate() const { return {}; }

  bool operator==(const GetNamespaceResponse&) const = default;
};

/// \brief Response body after updating namespace properties.
struct ICEBERG_REST_EXPORT UpdateNamespacePropertiesResponse {
  std::vector<std::string> updated;  // required
  std::vector<std::string> removed;  // required
  std::vector<std::string> missing;

  /// \brief Validates the UpdateNamespacePropertiesResponse.
  Status Validate() const { return {}; }

  bool operator==(const UpdateNamespacePropertiesResponse&) const = default;
};

/// \brief Response body for listing tables in a namespace.
struct ICEBERG_REST_EXPORT ListTablesResponse {
  PageToken next_page_token;
  std::vector<TableIdentifier> identifiers;

  /// \brief Validates the ListTablesResponse.
  Status Validate() const { return {}; }

  bool operator==(const ListTablesResponse&) const = default;
};

/// \brief Request to commit changes to a table.
struct ICEBERG_REST_EXPORT CommitTableRequest {
  TableIdentifier identifier;
  std::vector<std::shared_ptr<TableRequirement>> requirements;  // required
  std::vector<std::shared_ptr<TableUpdate>> updates;            // required

  /// \brief Validates the CommitTableRequest.
  Status Validate() const { return {}; }

  bool operator==(const CommitTableRequest& other) const;
};

/// \brief Response from committing changes to a table.
struct ICEBERG_REST_EXPORT CommitTableResponse {
  std::string metadata_location;            // required
  std::shared_ptr<TableMetadata> metadata;  // required

  /// \brief Validates the CommitTableResponse.
  Status Validate() const {
    if (metadata_location.empty()) {
      return ValidationFailed("Invalid metadata location: empty");
    }
    if (!metadata) {
      return ValidationFailed("Invalid metadata: null");
    }
    return {};
  }

  bool operator==(const CommitTableResponse& other) const;
};

/// \brief Response from an OAuth2 token endpoint.
struct ICEBERG_REST_EXPORT OAuthTokenResponse {
  std::string access_token;                // required
  std::string token_type;                  // required, "bearer" or "N_A"
  std::optional<int64_t> expires_in_secs;  // optional, seconds until expiration
  std::string issued_token_type;           // optional, for token exchange
  std::string refresh_token;               // optional
  std::string scope;                       // optional

  /// \brief Validates the token response.
  Status Validate() const;

  bool operator==(const OAuthTokenResponse&) const = default;
};

/// \brief Request to initiate a server-side scan planning operation.
struct ICEBERG_REST_EXPORT PlanTableScanRequest {
  std::optional<int64_t> snapshot_id;
  std::vector<std::string> select;
  std::shared_ptr<Expression> filter;
  bool case_sensitive = true;
  bool use_snapshot_schema = false;
  std::optional<int64_t> start_snapshot_id;
  std::optional<int64_t> end_snapshot_id;
  std::vector<std::string> stats_fields;
  std::optional<int64_t> min_rows_requested;

  Status Validate() const;

  bool operator==(const PlanTableScanRequest&) const;
};

/// \brief Response from initiating a scan planning operation, including plan status and
/// initial scan tasks.
struct ICEBERG_REST_EXPORT PlanTableScanResponse {
  std::optional<std::vector<std::string>> plan_tasks;
  std::optional<std::vector<std::shared_ptr<FileScanTask>>> file_scan_tasks;
  std::vector<std::shared_ptr<DataFile>> delete_files;
  PlanStatus plan_status = PlanStatus::kCompleted;
  std::string plan_id;
  std::optional<ErrorResponse> error;
  // TODO(sandeepg): Add credentials.

  Status Validate() const;

  bool operator==(const PlanTableScanResponse&) const;
};

/// \brief Response from polling an asynchronous scan plan, including current status and
/// available scan tasks.
struct ICEBERG_REST_EXPORT FetchPlanningResultResponse {
  std::optional<std::vector<std::string>> plan_tasks;
  std::optional<std::vector<std::shared_ptr<FileScanTask>>> file_scan_tasks;
  std::vector<std::shared_ptr<DataFile>> delete_files;
  PlanStatus plan_status = PlanStatus::kCompleted;
  std::optional<ErrorResponse> error;
  // TODO(sandeepg): Add credentials.

  Status Validate() const;

  bool operator==(const FetchPlanningResultResponse&) const;
};

/// \brief Request to fetch the scan tasks for a given plan task token.
struct ICEBERG_REST_EXPORT FetchScanTasksRequest {
  std::string planTask;

  Status Validate() const;

  bool operator==(const FetchScanTasksRequest&) const;
};

/// \brief Response containing the file scan tasks for a given plan task token.
struct ICEBERG_REST_EXPORT FetchScanTasksResponse {
  std::optional<std::vector<std::string>> plan_tasks;
  std::optional<std::vector<std::shared_ptr<FileScanTask>>> file_scan_tasks;
  std::vector<std::shared_ptr<DataFile>> delete_files;

  Status Validate() const;

  bool operator==(const FetchScanTasksResponse&) const;
};

}  // namespace iceberg::rest
