#include "revisions_gateway_impl.h"
#include "helpers.h"
#include "sql_strings.h"
#include "snapshot_id_impl.h"

#include <yandex/maps/wiki/revision/exception.h>
#include <yandex/maps/wiki/revision/branch.h>

namespace maps::wiki::revision {

using namespace helpers;

namespace {

const std::string STATEMENT_LOAD_MAX_COMMIT_ID =
    "SELECT max(" + sql::col::ID + ") FROM " + sql::table::COMMIT;

const std::string STATEMENT_LOAD_MAX_TRUNK_COMMIT_ID =
    STATEMENT_LOAD_MAX_COMMIT_ID +
    " WHERE " + sql::col::IS_TRUNK;

const std::string STATEMENT_LOAD_MAX_APPROVED_COMMIT_ID =
    STATEMENT_LOAD_MAX_COMMIT_ID +
    " WHERE " + sql::col::APPROVE_ORDER + " > 0";

std::string statementLoadMaxStableCommitId(revision::DBID branchId)
{
    return STATEMENT_LOAD_MAX_COMMIT_ID +
        " WHERE ("
        + sql::col::IS_TRUNK + " AND "
        + sql::col::STABLE_BRANCH_ID + "<" + std::to_string(branchId)
        + ") OR "
        + sql::col::STABLE_BRANCH_ID + "=" + std::to_string(branchId);
}


const std::string STATEMENT_LOAD_MAX_APPROVE_ORDER =
    "SELECT " +
        sql::col::APPROVE_ORDER + ", " +
        sql::col::ID + " " +
    "FROM " + sql::table::COMMIT + " " +
    "ORDER BY " + sql::col::APPROVE_ORDER + " DESC " +
    "LIMIT 1"
;

const std::string QUERY_TRUNCATE_ALL =
    "TRUNCATE " +
        sql::table::OBJECT_REVISION + ", " +
        sql::table::BRANCH + ", " +
        sql::table::COMMIT + ", " +
        sql::table::GEOMETRY + ", " +
        sql::table::DESCRIPTION + ";"
    "TRUNCATE " +
        sql::table::ATTRIBUTES + " CASCADE; " +
    "SELECT SETVAL(sequence_schema || '.' || sequence_name, minimum_value::bigint, false)"
    " FROM " + sql::table::SEQUENCES +
    " WHERE sequence_schema='" + sql::schema::REVISION + "';"
;

const std::string QUERY_CREATE_TRUNK_BRANCH =
    "INSERT INTO " + sql::table::BRANCH +
        " (" + sql::col::TYPE + "," +
               sql::col::STATE + "," + sql::col::CREATED_BY + ")"
        " VALUES ('" + sql::val::BRANCH_TYPE_TRUNK + "','" + sql::val::BRANCH_STATE_NORMAL + "',0)";

} // namespace


RevisionsGatewayImpl::RevisionsGatewayImpl(
        pqxx::transaction_base& work,
        BranchType type,
        DBID branchId)
    : reader_(std::make_shared<ReaderImpl>(work, type, branchId, DescriptionLoadingMode::Skip))
{}

void
RevisionsGatewayImpl::checkReadOnlyBranch() const
{
    const BranchType type = branchType();
    if (Branch::isReadOnlyType(type)) {
        throw BranchReadOnlyException()
            << "branch id: " << branchId() << " type: " << type;
    }
}

DBID
RevisionsGatewayImpl::headCommitId() const
{
    pqxx::result rows;
    switch (reader_->branchType()) {
        case BranchType::Trunk:
            rows = work().exec(STATEMENT_LOAD_MAX_TRUNK_COMMIT_ID);
            break;

        case BranchType::Approved:
            rows = work().exec(STATEMENT_LOAD_MAX_APPROVED_COMMIT_ID);
            break;

        case BranchType::Stable:
        case BranchType::Archive:
        case BranchType::Deleted:
            rows = work().exec(statementLoadMaxStableCommitId(branchId()));
            break;
    }

    if (rows.empty() || rows[0][0].is_null()) {
        return 0;
    }
    return rows[0][0].as<DBID>();
}

SnapshotId
RevisionsGatewayImpl::maxSnapshotId() const
{
    if (reader_->branchType() != BranchType::Approved) {
        DBID maxCommitId = headCommitId();
        return PImplFactory::create<SnapshotId>(
            std::nullopt,
            maxCommitId
        );
    }

    auto rows = work().exec(STATEMENT_LOAD_MAX_APPROVE_ORDER);
    if (rows.empty() && rows[0][0].is_null()) {
        return PImplFactory::create<SnapshotId>(NO_APPROVE_ORDER, NO_COMMIT_ID);
    }

    DBID maxApproveOrder = rows[0][0].as<DBID>();
    DBID maxCommitId = rows[0][1].as<DBID>();
    return PImplFactory::create<SnapshotId>(maxApproveOrder, maxCommitId);
}

void
RevisionsGatewayImpl::truncateAll() const
{
    pqxx::quiet_errorhandler disabler(work().conn());
    work().exec(QUERY_TRUNCATE_ALL);
}

void
RevisionsGatewayImpl::createDefaultBranches() const
{
    work().exec(QUERY_CREATE_TRUNK_BRANCH);
}

} // namespace maps::wiki::revision
