#include "branch_helpers.h"
#include "configs/config.h"
#include "exception.h"
#include "check_permissions.h"
#include <yandex/maps/wiki/revision/branch_manager.h>
#include <yandex/maps/wiki/common/compound_token.h>
#include <yandex/maps/wiki/common/revision_utils.h>
#include <maps/libs/common/include/exception.h>

namespace maps {
namespace wiki {

namespace {

void
setSearchPath(pqxx::transaction_base& work, TBranchId branchId)
{
    work.exec("SET search_path=" + vrevisionsSchemaName(branchId) + ",public");
}


const std::string VREVISIONS_PREFIX = "vrevisions_";
const std::string VREVISIONS_TRUNK = VREVISIONS_PREFIX + "trunk";
const std::string VREVISIONS_STABLE_PREFIX = VREVISIONS_PREFIX + "stable_";

inline std::string aliasView(TBranchId branchId)
{
    return common::CompoundToken::alias(branchId);
}

inline std::string aliasLabels(TBranchId branchId)
{
    return common::CompoundToken::aliasLabels(branchId);
}

Token
getViewToken(TBranchId branchId, const Token& token)
{
    return common::CompoundToken::subToken(token, aliasView(branchId));
}

} // namespace

Token
getSocialToken(const Token& token)
{
    return common::CompoundToken::subToken(
        token, common::TOKEN_DB_ALIAS_SOCIAL);
}

void
checkUnavailable(const revision::Branch& branch)
{
    WIKI_REQUIRE(
        branch.state() != revision::BranchState::Unavailable,
        ERR_BRANCH_UNAVAILABLE,
        "unavailable " << branch.type() << " branch" << ", id: " << branch.id()
    );
}

void
checkProgress(const revision::Branch& branch)
{
    WIKI_REQUIRE(
        branch.state() != revision::BranchState::Progress,
        ERR_BRANCH_IN_PROGRESS,
        branch.type() << " branch in progress mode" << ", id: " << branch.id()
    );
}

void
checkFinished(const revision::Branch& branch)
{
    WIKI_REQUIRE(
        !branch.finishedBy(),
        ERR_BRANCH_ALREADY_FINISHED,
        branch.type() << " branch already finished" <<
        ", id: " << branch.id() <<
        ", finished by: " << branch.finishedBy();
    );
}

bool
hasStabeBranchCreationAttribute(const revision::Branch& branch)
{
    const auto& attributes = branch.attributes();
    auto it = attributes.find(STABLE_BRANCH_CREATION_ATTR);
    return it != attributes.end() && it->second == "1";
}

std::string
vrevisionsSchemaName(TBranchId branchId)
{
    if (branchId == revision::TRUNK_BRANCH_ID) {
        return VREVISIONS_TRUNK;
    }
    return VREVISIONS_STABLE_PREFIX + std::to_string(branchId);
}

Branches
loadHeadBranches(pqxx::transaction_base& work)
{
    revision::BranchManager branchManager(work);
    auto branches = branchManager.load(
        {{revision::BranchType::Trunk, 1},
         {revision::BranchType::Approved, 1},
         {revision::BranchType::Stable, 1},
         {revision::BranchType::Archive, branchManager.UNLIMITED}});
    REQUIRE(!branches.empty(), "loading branches failed");
    return branches;
}

BranchContext::BranchContext(
        revision::Branch branch,
        AccessMode accessMode,
        SharedTransaction workCore,
        SharedTransaction workSocial,
        SharedTransaction workView)
    : branch(std::move(branch))
    , accessMode(accessMode)
    , workCore_(std::move(workCore))
    , optionalTransactions_(std::make_shared<OptionalTransactions>())
{
    ASSERT(workCore_);
    optionalTransactions_->workSocial = std::move(workSocial);
    optionalTransactions_->workView = std::move(workView);
    if (optionalTransactions_->workView) {
        setSearchPath(*optionalTransactions_->workView, this->branch.id());
    }
}

pqxx::transaction_base&
BranchContext::txnSocial() const
{
    if (!optionalTransactions_->workSocial) {
        ASSERT(accessMode == AccessMode::ReadWrite);
        optionalTransactions_->workSocial = cfg()->poolSocial().masterWriteableTransaction();
    }
    return *optionalTransactions_->workSocial;
}

pqxx::transaction_base&
BranchContext::txnView() const
{
    if (!optionalTransactions_->workView) {
        ASSERT(accessMode == AccessMode::ReadWrite);
        optionalTransactions_->workView =
            BranchContextFacade::acquireWorkWriteViewOnly(branch.id());
    }
    return *optionalTransactions_->workView;
}

pqxx::transaction_base&
BranchContext::txnLabels() const
{
    ASSERT(accessMode == AccessMode::ReadWrite);

    if (!optionalTransactions_->workLabels) {
        optionalTransactions_->workLabels =
            BranchContextFacade::acquireWorkWriteLabelsOnly(branch.id());
    }
    return *optionalTransactions_->workLabels;
}

Token
BranchContext::commit()
{
    ASSERT(accessMode == AccessMode::ReadWrite);

    //<tsCore>:<idCore>:core
    //.<tsSocial>:<idSocial>:social
    //.<tsView>:<idView>:<aliasView>
    //.<tsLabels>:<idLabels>:<aliasLabels>

    common::CompoundToken compoundToken;
    compoundToken.append(
        common::TOKEN_DB_ALIAS_CORE,
        pgpool3::generateToken(*workCore_));

    if (optionalTransactions_->workSocial) {
        compoundToken.append(
            common::TOKEN_DB_ALIAS_SOCIAL,
            pgpool3::generateToken(*optionalTransactions_->workSocial));
    }

    if (optionalTransactions_->workView) {
        compoundToken.append(
            aliasView(branch.id()),
            pgpool3::generateToken(*optionalTransactions_->workView));
    }

    if (optionalTransactions_->workLabels) {
        compoundToken.append(
            aliasLabels(branch.id()),
            pgpool3::generateToken(*optionalTransactions_->workLabels));
    }

    workCore_->commit();
    if (optionalTransactions_->workSocial) {
        optionalTransactions_->workSocial->commit();
    }
    if (optionalTransactions_->workView) {
        optionalTransactions_->workView->commit();
    }
    if (optionalTransactions_->workLabels) {
        optionalTransactions_->workLabels->commit();
    }
    return compoundToken.str();
}

void
BranchContext::releaseViewTxn()
{
    ASSERT(accessMode == AccessMode::ReadWrite);

    if (optionalTransactions_->workView) {
        optionalTransactions_->workView = {};
    }
}

BranchContext
BranchContextFacade::acquireManageBranches(TUid uid) const
{
    auto branchCtx = acquireWrite();
    CheckPermissions(uid, branchCtx.txnCore()).checkPermissionsToManageBranches();

    return branchCtx;
}

BranchContext
BranchContextFacade::acquireRead(TBranchId branchId, const Token& token)
{
    auto& poolCore = cfg()->poolCore();
    auto& poolSocial = cfg()->poolSocial();
    auto& poolView = cfg()->poolView(branchId);

    auto workCore = poolCore.slaveTransaction(token);
    auto branch = revision::BranchManager(*workCore).load(branchId);

    checkUnavailable(branch);

    return {
        std::move(branch),
        AccessMode::ReadOnly,
        std::move(workCore),
        poolSocial.slaveTransaction(getSocialToken(token)),
        poolView.slaveTransaction(getViewToken(branchId, token))
    };
}

BranchContext
BranchContextFacade::acquireWrite(TBranchId branchId, TUid uid)
{
    auto& poolCore = cfg()->poolCore();

    auto workCore = poolCore.masterWriteableTransaction();
    auto branch = revision::BranchManager(*workCore).load(branchId);

    checkUnavailable(branch);
    checkProgress(branch);
    CheckPermissions(uid, *workCore).checkPermissionsToEditBranch(branchId);

    return {
        std::move(branch),
        AccessMode::ReadWrite,
        std::move(workCore)
    };
}

BranchContext
BranchContextFacade::acquireReadCoreView(const Token& token) const
{
    auto& poolCore = cfg()->poolCore();
    auto& poolView = cfg()->poolView(branchId);

    auto workCore = poolCore.slaveTransaction(token);
    auto branch = revision::BranchManager(*workCore).load(branchId);

    check(branch);

    return {
        std::move(branch),
        AccessMode::ReadOnly,
        std::move(workCore),
        {},
        poolView.slaveTransaction(getViewToken(branchId, token))
    };
}

BranchContext
BranchContextFacade::acquireLongReadCoreOnly(TCommitId commitId) const
{
    REQUIRE(commitId, "invalid commit id");

    auto& poolCore = cfg()->poolCore();

    auto workCore = common::getReadTransactionForCommit(
        poolCore, branchId, commitId,
        [](const std::string& msg) { INFO() << msg; });

    auto branch = revision::BranchManager(*workCore).load(branchId);
    return {
        std::move(branch),
        AccessMode::ReadOnly,
        std::move(workCore)
    };
}

BranchContext
BranchContextFacade::acquireWrite() const
{
    auto& poolCore = cfg()->poolCore();

    auto workCore = poolCore.masterWriteableTransaction();
    auto branch = revision::BranchManager(*workCore).load(branchId);

    check(branch);

    return {
        std::move(branch),
        AccessMode::ReadWrite,
        std::move(workCore)
    };
}

BranchContext
BranchContextFacade::acquireReadCoreSocial(const Token& token) const
{
    auto& poolCore = cfg()->poolCore();
    auto& poolSocial = cfg()->poolSocial();

    auto workCore = poolCore.slaveTransaction(token);
    auto branch = revision::BranchManager(*workCore).load(branchId);

    check(branch);

    return {
        std::move(branch),
        AccessMode::ReadOnly,
        std::move(workCore),
        poolSocial.slaveTransaction(getSocialToken(token))
    };
}

pgpool3::TransactionHandle
BranchContextFacade::acquireWorkReadViewOnly(TBranchId branchId, const Token& token)
{
    auto& poolView = cfg()->poolView(branchId);

    auto workView = poolView.slaveTransaction(getViewToken(branchId, token));

    setSearchPath(*workView, branchId);
    return workView;
}

pgpool3::TransactionHandle
BranchContextFacade::acquireWorkWriteViewOnly(TBranchId branchId)
{
    auto& poolView = cfg()->poolView(branchId);

    auto workView = poolView.masterWriteableTransaction();
    setSearchPath(*workView, branchId);
    return workView;
}

pgpool3::TransactionHandle
BranchContextFacade::acquireWorkWriteLabelsOnly(TBranchId branchId)
{
    auto& poolLabels = cfg()->poolLabels(branchId);

    auto workLabels = poolLabels.masterWriteableTransaction();
    setSearchPath(*workLabels, branchId);
    return workLabels;
}

void
BranchContextFacade::check(const revision::Branch& branch) const
{
    if (checkBranchState == CheckBranchState::CheckNormal) {
        checkUnavailable(branch);
        checkProgress(branch);
    }
}

} // namespace wiki
} // namespace maps
