/**
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * This file is part of osm2pgsql (https://osm2pgsql.org/).
 *
 * Copyright (C) 2006-2026 by the osm2pgsql developer community.
 * For a full list of authors see the git log.
 */

#include <catch.hpp>

#include "properties.hpp"

#include "common-pg.hpp"

TEST_CASE("Store and retrieve properties (memory only)")
{
    properties_t properties{connection_params_t{}, "public"};

    properties.set_string("foo", "firstvalue");
    properties.set_string("foo", "bar"); // overwriting is okay
    properties.set_string("number", "astring");
    properties.set_int("number", 123); // overwriting with other type okay
    properties.set_bool("decide", true);
    properties.set_string("empty", ""); // empty string is okay

    REQUIRE(properties.get_string("foo", "baz") == "bar");
    REQUIRE(properties.get_string("something", "baz") == "baz");
    REQUIRE(properties.get_string("empty", "baz").empty());
    REQUIRE_THROWS(properties.get_int("foo", 1));
    REQUIRE_THROWS(properties.get_bool("foo", true));

    REQUIRE(properties.get_int("number", 42) == 123);
    REQUIRE(properties.get_int("anumber", 42) == 42);
    REQUIRE(properties.get_string("number", "x") == "123");
    REQUIRE_THROWS(properties.get_bool("number", true));

    REQUIRE(properties.get_bool("decide", false));
    REQUIRE(properties.get_bool("unknown", true));
    REQUIRE_FALSE(properties.get_bool("unknown", false));
    REQUIRE(properties.get_string("decide", "x") == "true");
    REQUIRE_THROWS(properties.get_int("decide", 123));
}

TEST_CASE("Store and retrieve properties (with database)")
{
    for (std::string const schema : {"public", "middleschema"}) {
        testing::pg::tempdb_t const db;
        auto const conn = db.connect();

        if (schema != "public") {
            conn.exec("CREATE SCHEMA IF NOT EXISTS {};", schema);
        }

        {
            properties_t properties{db.connection_params(), schema};
            properties.init_table();

            properties.set_string("foo", "bar");
            properties.set_string("empty", "");
            properties.set_int("number", 123);
            properties.set_bool("decide", true);

            properties.store();
        }

        {
            init_database_capabilities(conn);
            std::string const full_table_name =
                (schema.empty() ? "" : schema + ".") + "osm2pgsql_properties";

            REQUIRE(conn.get_count(full_table_name) == 4);
            REQUIRE(conn.get_count(full_table_name,
                                   "property='foo' AND value='bar'") == 1);
            REQUIRE(conn.get_count(full_table_name,
                                   "property='empty' AND value=''") == 1);
            REQUIRE(conn.get_count(full_table_name,
                                   "property='number' AND value='123'") == 1);
            REQUIRE(conn.get_count(full_table_name,
                                   "property='decide' AND value='true'") == 1);

            properties_t properties{db.connection_params(), schema};
            REQUIRE(properties.load());

            REQUIRE(properties.get_string("foo", "baz") == "bar");
            REQUIRE(properties.get_string("something", "baz") == "baz");
            REQUIRE(properties.get_string("empty", "baz").empty());
            REQUIRE_THROWS(properties.get_int("foo", 1));
            REQUIRE_THROWS(properties.get_bool("foo", true));

            REQUIRE(properties.get_int("number", 42) == 123);
            REQUIRE(properties.get_int("anumber", 42) == 42);
            REQUIRE(properties.get_string("number", "x") == "123");
            REQUIRE_THROWS(properties.get_bool("number", true));

            REQUIRE(properties.get_bool("decide", false));
            REQUIRE(properties.get_bool("unknown", true));
            REQUIRE_FALSE(properties.get_bool("unknown", false));
            REQUIRE(properties.get_string("decide", "x") == "true");
            REQUIRE_THROWS(properties.get_int("decide", 123));
        }
    }
}

TEST_CASE("Update existing properties in database")
{
    testing::pg::tempdb_t const db;
    auto const conn = db.connect();

    {
        properties_t properties{db.connection_params(), "public"};
        properties.init_table();

        properties.set_string("a", "xxx");
        properties.set_string("b", "yyy");

        properties.store();
    }

    {
        init_database_capabilities(conn);
        REQUIRE(conn.get_count("osm2pgsql_properties") == 2);

        properties_t properties{db.connection_params(), "public"};
        REQUIRE(properties.load());

        REQUIRE(properties.get_string("a", "def") == "xxx");
        REQUIRE(properties.get_string("b", "def") == "yyy");

        properties.set_string("a", "zzz");
        properties.set_string("b", "zzz");

        // both are updated in memory
        REQUIRE(properties.get_string("a", "def") == "zzz");
        REQUIRE(properties.get_string("b", "def") == "zzz");

        properties.store();
    }

    {
        REQUIRE(conn.get_count("osm2pgsql_properties") == 2);

        properties_t properties{db.connection_params(), "public"};
        REQUIRE(properties.load());

        // both are updated in the database
        REQUIRE(properties.get_string("a", "def") == "zzz");
        REQUIRE(properties.get_string("b", "def") == "zzz");
    }
}

TEST_CASE("Load returns false if there are no properties in database")
{
    testing::pg::tempdb_t const db;
    auto const conn = db.connect();
    init_database_capabilities(conn);

    properties_t properties{db.connection_params(), "public"};
    REQUIRE_FALSE(properties.load());
}
