/*
 * 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 <exception>
#include <memory>
#include <string_view>

#include <sqlpp23/postgresql/postgresql.h>

#include "iceberg/catalog/sql/catalog_store.h"
#include "iceberg/catalog/sql/catalog_store_sqlpp23_internal.h"
#include "iceberg/catalog/sql/connection_uri_internal.h"
#include "iceberg/result.h"
#include "iceberg/util/macros.h"

namespace iceberg::sql {

namespace {

/// Recognizes PostgreSQL unique-constraint violations via SQLSTATE 23505
/// (unique_violation), reported on `result_exception`.
struct PostgresqlTraits {
  static bool IsUniqueViolation(const std::exception& error) {
    const auto* result_error =
        dynamic_cast<const sqlpp::postgresql::result_exception*>(&error);
    return result_error != nullptr && result_error->sql_state() == "23505";
  }

  static bool IsDuplicateColumn(const std::exception& error) {
    const auto* result_error =
        dynamic_cast<const sqlpp::postgresql::result_exception*>(&error);
    return result_error != nullptr && result_error->sql_state() == "42701";
  }
};

using PostgresqlSingleCatalogStore =
    Sqlpp23CatalogStore<SingleConnectionSource<sqlpp::postgresql::connection>,
                        PostgresqlTraits>;
using PostgresqlPooledCatalogStore =
    Sqlpp23CatalogStore<PooledConnectionSource<sqlpp::postgresql::connection_pool>,
                        PostgresqlTraits>;

}  // namespace

Result<std::shared_ptr<CatalogStore>> MakePostgreSqlCatalogStore(
    const CatalogStoreOptions& options) {
  ICEBERG_ASSIGN_OR_RAISE(const auto parsed, ParseConnectionUri(options.uri));

  auto config = std::make_shared<sqlpp::postgresql::connection_config>();
  config->host = parsed.host;
  if (parsed.port.has_value()) {
    config->port = *parsed.port;
  }
  config->dbname = parsed.database;
  config->user = parsed.user;
  config->password = parsed.password;

  try {
    if (options.max_connections > 1) {
      sqlpp::postgresql::connection_pool pool(
          config, static_cast<std::size_t>(options.max_connections));
      return std::make_shared<PostgresqlPooledCatalogStore>(
          options.catalog_name, std::move(pool), options.max_connections);
    }

    sqlpp::postgresql::connection connection(config);
    return std::make_shared<PostgresqlSingleCatalogStore>(options.catalog_name,
                                                          std::move(connection));
  } catch (const std::exception& e) {
    return IOError("Failed to connect to PostgreSQL '{}': {}", options.uri, e.what());
  }
}

}  // namespace iceberg::sql
