#include <maps/libs/xml/include/xml.h>
#include <maps/wikimap/mapspro/libs/acl/include/aclgateway.h>
#include <maps/wikimap/mapspro/libs/acl/include/acl_tool.h>
#include <maps/wikimap/mapspro/libs/acl/include/exception.h>
#include <yandex/maps/wiki/common/string_utils.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/pgpool/include/pgpool3.h>
#include <yandex/maps/pgpool3utils/dynamic_pool_holder.h>

#include <iostream>

namespace acl = maps::wiki::acl;
namespace cmn = maps::wiki::common;

namespace {
const std::string DEFAULT_PERMISSIONS_PATH = "/usr/share/yandex/maps/wiki/editor/permissions.xml";
const std::string DEFAULT_MAPS_WIKI_SERVICES_CONFIG_PATH = "/etc/yandex/maps/wiki/services/services.xml";
} // namespace

struct PermissionRoles
{
    acl::ID permissionID;
    std::string xpath;
    std::string path;
    size_t depth;
    std::vector<acl::Role> roles;
};

typedef std::multimap<size_t, PermissionRoles> PermissionRolesByDepth;

PermissionRolesByDepth
findPermissionsMissingInXml(maps::xml3::Doc& doc, acl::ACLGateway& acl)
{
    auto permissions = acl.allPermissions();
    auto roots = permissions.roots();
    PermissionRolesByDepth missing;
    std::list<acl::Permission> toCheck(roots.begin(), roots.end());
    while (!toCheck.empty()) {
        auto curPermission = toCheck.front();
        auto path = permissions.path(curPermission);
        auto xpath = "//permissions/" +
            maps::wiki::common::join(path,
                [](const std::string& permissionId)
                {
                    return std::string("permission[@id='") + permissionId + "']";
                },
                "/"
            );
        if (doc.node(xpath, true).isNull()) {
            missing.insert(
                {
                    path.size(),
                    PermissionRoles {
                        curPermission.id(),
                        xpath,
                        maps::wiki::common::join(path, '/'),
                        path.size(),
                        curPermission.roles()
                    }
                }
            );
        }
        auto next = permissions.children(curPermission);
        toCheck.pop_front();
        toCheck.insert(toCheck.end(), next.begin(), next.end());
    }
    return missing;
}

void
deletePermissionsMissingInXml(PermissionRolesByDepth&& toDelete, acl::ACLGateway& acl)
{
    std::cout << "Deleting." << std::endl;
    for (auto it = toDelete.rbegin(); it != toDelete.rend(); ++it) {
        std::cout << "Deleting " << it->second.path << std::endl;
        auto permission = acl.permission(it->second.permissionID);
        for (auto& role : it->second.roles) {
            role.remove(permission);
        }
        acl.drop(std::move(permission));
    }
}

void
dumpPermissionsMissingInXml(const PermissionRolesByDepth& toDelete)
{
    std::cout << "There are permisions which are not present in new XML: " << std::endl;
    for (const auto& pair : toDelete) {
        const auto& permissionRoles = pair.second;
        std::cout << "  Permission id: " << permissionRoles.permissionID << std::endl;
        std::cout << "    xpath " << permissionRoles.xpath << std::endl;
        std::cout << "    path " << permissionRoles.path << std::endl;
        std::cout << "      Assigned to roles: " << std::endl;
        if (permissionRoles.roles.empty()) {
            std::cout << "        None" << std::endl;
        } else {
            for (const auto& role : permissionRoles.roles) {
                std::cout << "        " << role.name() << std::endl;
            }
        }
    }
}

int
main(int argc, char** argv)
{
    maps::cmdline::Parser argsParser("\n\twiki-acl-tool --default[ --config <path>][ --permissions <path>]"
        "\n\twiki-acl-tool --config <path> --permissions <path>");
    auto defaultRun = argsParser.flag("default")
        .help("Run using default paths for config and permissions files. Explicit path setting will override default one");
    auto clusterizeRequested = argsParser.flag("clusterize")
        .help("Build user and groups permissions search clusters. Long job, use screen!");
    auto clusterizeAllRequested = argsParser.flag("clusterize-all")
        .help("Build user and groups permissions search clusters from scratch. Long job, use screen!");
    auto configPath = argsParser.string("config")
        .defaultValue(DEFAULT_MAPS_WIKI_SERVICES_CONFIG_PATH)
        .help("Path to config.xml with database connection settings")
        .metavar("<path>");
    auto permissionsPath = argsParser.string("permissions")
        .defaultValue(DEFAULT_PERMISSIONS_PATH)
        .help("Path to permissions.xml")
        .metavar("<path>");
    argsParser.parse(argc, argv);
    if (!defaultRun.defined() && (!configPath.defined() || !permissionsPath.defined())) {
        argsParser.printUsageAndExit(EXIT_FAILURE);
    }

    std::cout << "Config file     : " << configPath << "\n";
    std::cout << "Permissions file: " << permissionsPath << "\n";

    auto configDoc = maps::xml3::Doc::fromFile(configPath);
    maps::pgp3utils::DynamicPoolHolder poolHolder(configDoc.node("/config//database[@id='core']"), "core");
    auto transactionHandle = poolHolder.pool().masterWriteableTransaction();
    acl::ACLGateway gateway(transactionHandle.get());
    auto doc = maps::xml3::Doc::fromFile(permissionsPath);
    auto toDelete = findPermissionsMissingInXml(doc, gateway);
    bool hasUpdates = false;
    if (!toDelete.empty()) {
        dumpPermissionsMissingInXml(toDelete);
        char command = 0;
        while (command != 'S' && command != 'D' && command != 'C') {
            std::cout << "(S)kip and continue adding, (D)elete from database then add, (C)ancel operation:";
            command = std::cin.get();
            std::cout << std::endl;
        }
        if (command == 'C') {
            std::cout << "Canceling." << std::endl;
            return 0;
        }
        if (command == 'S') {
            std::cout << "Skipping." << std::endl;
        }
        if (command == 'D') {
            hasUpdates = true;
            deletePermissionsMissingInXml(std::move(toDelete), gateway);
        }
    }
    hasUpdates = acl::fillPermissionsTree(gateway, doc) || hasUpdates;
    if (hasUpdates || clusterizeRequested || clusterizeAllRequested) {
        std::cout << "Updating permissions leafs." << std::endl;
        auto allPermissions = gateway.allPermissions();
        for (const auto& root : allPermissions.roots()) {
            gateway.permission(root.id()).updateLeafsIds(gateway);
        }
        std::cout << "Done updating permissions leafs." << std::endl;
    }
    if (hasUpdates) {
        std::cout << "Enqueueing clusters permissions leafs." << std::endl;
        gateway.enqueueAllClusters();
    }
    if (clusterizeRequested || clusterizeAllRequested) {
        std::cout << "Clusterize requested. This will be long job. Use screen!" << std::endl;
        std::cout << "Building clusters." << std::endl;
        gateway.updateAgentsClusters(!clusterizeAllRequested);
        std::cout << "Done building clusters." << std::endl;
    }
    transactionHandle->commit();
    return EXIT_SUCCESS;
}
