#include "util.h"

#include <maps/libs/common/include/file_utils.h>
#include <yandex/maps/wiki/unittest/default_extensions.h>

#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>

#include <algorithm>
#include <functional>
#include <iostream>
#include <vector>

namespace fs = boost::filesystem;

namespace maps {
namespace wiki {
namespace unittest {

namespace {

const std::string MIGRATIONS_UPGRADE_PATTERN = "_upgrade.sql";

std::vector<std::string> dirFilenames(const fs::path& path)
{
    std::vector<std::string> result;
    fs::directory_iterator end;
    for (fs::directory_iterator itr(path); itr != end; ++itr) {
        if (fs::is_regular_file(itr->status())) {
            result.push_back(itr->path().string());
        }
    }
    return result;
}

std::vector<std::string> readNonSystemSchemas(pqxx::connection& conn)
{
    pqxx::work work(conn);
    auto r = work.exec(
        "SELECT nspname FROM pg_catalog.pg_namespace"
        " WHERE nspname NOT LIKE 'pg_%'"
        " AND nspname <> 'information_schema'"
        );
    std::vector<std::string> result;
    for (const auto& row: r) {
        result.push_back(row[0].as<std::string>());
    }
    return result;
}

} // namespace

void dropNonSystemSchemas(pqxx::connection& conn)
{
    for (auto&& schema: readNonSystemSchemas(conn)) {
        pqxx::work work(conn);
        work.exec("DROP SCHEMA " + schema + " CASCADE;");
        work.commit();
    }
}

void createPublicSchema(pqxx::connection& conn)
{
    pqxx::work work(conn);
    std::ostringstream query;
    query << "CREATE SCHEMA public;\n";
    for (const auto& ext : defaultPsqlExtensions()) {
        query << "CREATE EXTENSION " << ext << ";\n";
    }
    work.exec(query.str() );
    work.commit();
}

void applyMigrations(pqxx::connection& conn, const std::string& dir)
{
    auto filenames = dirFilenames(dir);

    filenames.erase(
        std::remove_if(
            filenames.begin(), filenames.end(),
            [](const std::string& filename) {
                return !filename.ends_with(MIGRATIONS_UPGRADE_PATTERN);
            }
        ),
        filenames.end()
    );

    std::sort(filenames.begin(), filenames.end());

    for (const auto& filename: filenames) {
        applySqlFile(conn, filename, boost::none);
    }
}

pqxx::result applyQuery(
    pqxx::connection& conn, const std::string& query,
    const boost::optional<std::string>& schema)
{
    pqxx::work txn(conn);
    auto result = schema
        ? txn.exec("SET search_path = " + *schema + ", public;" + query)
        : txn.exec(query);
    txn.commit();
    return result;
}

pqxx::result applySqlFile(
    pqxx::connection& conn, const std::string& filename,
    const boost::optional<std::string>& schema)
{
    return applyQuery(
        conn,
        boost::replace_all_copy(maps::common::readFileToString(filename), "%%", "%"),
        schema);
}

void applySqlFiles(pqxx::connection& conn, const std::string& dir)
{
    for (const auto& filename: dirFilenames(dir)) {
        applySqlFile(conn, filename, boost::none);
    }
}

std::string makeLockIdStr(const std::string& tag)
{
    std::hash<std::string> hash;
    return "'" + std::to_string(static_cast<int64_t>(hash(tag))) + "'::bigint";
}

} // unittest
} // wiki
} // maps
