#include "ymapsdf_fixture.h"

#include <library/cpp/testing/unittest/env.h>
#include <maps/libs/log8/include/log8.h>
#include <yandex/maps/wiki/common/string_utils.h>

namespace maps::wiki::tests {

const std::string YMAPSDF_EXPORT_FILE = BinaryPath("maps/doc/schemas/ymapsdf/package/ymapsdf_export.sql");
const std::string YMAPSDF_CREATE_FILE = BinaryPath("maps/doc/schemas/ymapsdf/package/ymapsdf_create.sql");


YmapsdfFixture::YmapsdfFixture(
    pgpool3::ConnectionHandle connection,
    const std::string& schema,
    const std::string& sqlFile
)
    : ConnHolder(std::move(connection))
    , QueryHelpers(conn.get())
    , SCHEMA(schema)
{
    log8::setLevel(log8::Level::WARNING);

    applyQuery("CREATE SCHEMA " + SCHEMA);
    applySqlFile(sqlFile, SCHEMA);
}


YmapsdfFixture::~YmapsdfFixture() {
    applyQuery("DROP SCHEMA IF EXISTS " + SCHEMA + " CASCADE;");
}


void
YmapsdfFixture::cleanTable(const std::string& table) {
    applyQueryToSchema(SCHEMA, "TRUNCATE TABLE " + table);
}


void
YmapsdfFixture::dropForeignKeys() {
    const char FOREIGN_KEY = 'f';

    applyQuery(
        "DO $$\n"
        "DECLARE row record;\n"
        "BEGIN\n"
        "  FOR row IN\n"
        "    SELECT\n"
        "      relname AS table_name,\n"
        "      conname AS constraint_name\n"
        "    FROM pg_constraint\n"
        "    JOIN pg_namespace ON (pg_constraint.connamespace = pg_namespace.oid)\n"
        "    JOIN pg_class ON (pg_constraint.conrelid = pg_class.oid)\n"
        "    WHERE pg_namespace.nspname = '" + SCHEMA + "'\n"
        "      AND pg_constraint.contype='" + FOREIGN_KEY + "'\n"
        "  LOOP\n"
        "    EXECUTE\n"
        "      'ALTER TABLE " + SCHEMA + ".' || quote_ident(row.table_name) ||\n"
        "      ' DROP CONSTRAINT ' || quote_ident(row.constraint_name) || ';';\n"
        "  END LOOP;\n"
        "END\n"
        "$$;"
    );
}


void
YmapsdfFixture::dropNotNullChecks() {
    applyQuery(
        "DO $$\n"
        "DECLARE row record;\n"
        "BEGIN\n"
        "  FOR row IN\n"
        "    SELECT table_name, column_name\n"
        "    FROM information_schema.columns\n"
        "    WHERE table_schema = '" + SCHEMA + "'\n"
        "      AND is_nullable = 'NO'\n"
        "    EXCEPT\n"
        "    SELECT table_name, column_name\n"
        "    FROM information_schema.table_constraints\n"
        "    LEFT JOIN information_schema.constraint_column_usage\n"
        "      USING(table_catalog, table_schema, table_name, constraint_catalog, constraint_schema, constraint_name)\n"
        "    WHERE table_schema = '" + SCHEMA + "'\n"
        "      AND constraint_type = 'PRIMARY KEY'\n"
        "  LOOP\n"
        "    EXECUTE\n"
        "      'ALTER TABLE " + SCHEMA + ".' || quote_ident(row.table_name) || \n"
        "      ' ALTER ' || quote_ident(row.column_name) || ' DROP NOT NULL;';\n"
        "  END LOOP;\n"
        "END\n"
        "$$;"
    );
}


boost::test_tools::predicate_result
YmapsdfFixture::hasIsocodes(const std::string& category, size_t id, const std::set<std::string>& expected) {
    auto rows = applyQueryToSchema(
        SCHEMA,
        "SELECT isocode FROM " + category + "_isocode "
        "WHERE " + category + "_id = " + std::to_string(id) + " "
        "ORDER BY 1"
    );

    std::set<std::string> got;
    for (const auto& row: rows) {
        got.insert(row["isocode"].as<std::string>());
    }

    return compare(expected, got);
}


boost::test_tools::predicate_result
YmapsdfFixture::tableHasOnlyIds(const std::string& table, const std::string& idColumn, const std::set<size_t>& expected) {
    auto rows = applyQueryToSchema(SCHEMA, "SELECT " + idColumn + " FROM " + table);

    std::set<size_t> got;
    for (const auto& row: rows) {
        got.insert(row[0].as<size_t>());
    }

    return compare(expected, got);
}


template<typename T>
boost::test_tools::predicate_result
YmapsdfFixture::compare(const std::set<T>& expected, const std::set<T>& got) {
    if (expected.size() != got.size()) {
        boost::test_tools::predicate_result result(false);
        result.message()
            << "\nDifferent set sizes "
            << expected.size() << " (exp) != " << got.size() << " (got):\n"
            << expectedVsGot(expected, got);
        return result;
    }

    for (const auto& id: expected) {
        if (!got.count(id)) {
            boost::test_tools::predicate_result result(false);
            result.message()
                << "\nExpected '" << id << "' is absent:\n"
                << expectedVsGot(expected, got);
            return result;
        }
    }

    return true;
}


template<typename T>
std::string
YmapsdfFixture::expectedVsGot(const std::set<T>& expected, const std::set<T>& got) {
    std::ostringstream oss;
    oss << "- expected: '" << common::join(expected, "', '") << "';\n"
        << "- got:      '" << common::join(got, "', '") << "'.";
    return oss.str();
}

} // namespace maps::wiki::tests
