#include "parse.h"
#include <maps/wikimap/mapspro/services/dataset-explorer/lib/json.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/infra/yacare/include/yacare.h>

#include <yandex/maps/wiki/common/default_config.h>
#include <yandex/maps/wiki/common/extended_xml_doc.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>
#include <yandex/maps/wiki/mds_dataset/dataset_gateway.h>
#include <yandex/maps/wiki/mds_dataset/export_metadata.h>
#include <yandex/maps/mds/mds.h>

#include <boost/optional.hpp>

#include <memory>
#include <string>
#define API_VERSION  "v0.1"

namespace common = maps::wiki::common;
namespace mlog = maps::log8;
namespace md = maps::wiki::mds_dataset;
namespace dexp = maps::wiki::dataset_explorer;

namespace {

const std::string APP_NAME = "dataset-explorer";
const std::string JSON_MIME = "application/json";

std::unique_ptr<common::PoolHolder> g_dbHolder;

void errorReporter(const yacare::Request&, yacare::Response& resp)
{
    try {
        throw;
    } catch (const yacare::Error& e) {
        resp.setStatus(yacare::HTTPStatus::find(e.status()));
        resp << e.what();
        ERROR() << e;
    } catch (const maps::Exception& e) {
        resp.setStatus(yacare::HTTPStatus::InternalError);
        resp << e.what();
        ERROR() << e;
    } catch (const std::exception& e) {
        resp.setStatus(yacare::HTTPStatus::InternalError);
        resp << e.what();
        ERROR() << "Internal error: " << e.what();
    } catch (...) {
        resp.setStatus(yacare::HTTPStatus::InternalError);
        resp << "unknown error";
    }
}

} // anonymous namespace

yacare::VirtualHost vhost {
    yacare::VirtualHost::SLB { "dataset-explorer" },
    yacare::VirtualHost::SLB { "dataset-explorer.um" },
    yacare::VirtualHost::SLB { "core-nmaps-dataset-explorer" },
    yacare::VirtualHost::FQDN { "core-nmaps-dataset-explorer.maps.n.yandex.ru" },
    yacare::VirtualHost::FQDN { "core-nmaps-dataset-explorer.testing.maps.n.yandex.ru" },
    yacare::VirtualHost::Real { "dataset-explorer.um-back" },
};

YCR_SET_DEFAULT(vhost)

YCR_RESPOND_TO("GET /"  API_VERSION  "/export-latest/$/$")
{
    auto subset = pathnameParam<md::Subset>(0);
    auto fileName = pathnameParam<std::string>(1);

    auto txn = g_dbHolder->pool().slaveTransaction();
    md::ExportFilter filter(*txn);
    filter.bySubset(subset).byStatus(md::DatasetStatus::Available);

    auto datasets = md::DatasetReader<md::ExportMetadata>::datasets(
        *txn, filter, /* limit = */ 1);

    if (datasets.empty()) {
        throw yacare::errors::NotFound()
            << "No available datasets for subset " << subset << " found";
    }

    for (const auto& fileLink : datasets[0].fileLinks()) {
        if (fileLink.name() == fileName) {
            response.setStatus(yacare::HTTPStatus::SeeOther);
            response.setHeader("Location", fileLink.readingUrl());
            return;
        }
    }

    throw yacare::errors::NotFound() << "Unknown file name " << fileName;
}

YCR_RESPOND_TO("GET /" API_VERSION "/export/$")
{
    auto txn = g_dbHolder->pool().slaveTransaction();

    auto subset = pathnameParam<md::Subset>(0);

    auto status = dexp::getOptionalParam<md::DatasetStatus>(request, "status");
    auto offset = dexp::getParam<uint64_t>(request, "offset", 0);
    auto limit = dexp::getParam<uint32_t>(request, "limit", 150);
    auto tested = dexp::getOptionalParam<bool>(request, "tested");

    md::ExportFilter filter(*txn);
    filter.bySubset(subset);

    if (status) {
        filter.byStatus(*status);
    }
    if (tested) {
        filter.tested(*tested);
    }

    auto datasets = md::DatasetReader<md::ExportMetadata>::datasets(
        *txn, filter, limit, offset);

    response.setStatus(yacare::HTTPStatus::OK);
    response["Content-Type"] = JSON_MIME;
    response << dexp::makeJsonDatasetsList(datasets, offset, limit);
}

YCR_MAIN(argc, argv)
try {
    maps::cmdline::Parser parser;
    auto config = parser.string("config")
        .help("path to configuration");
    parser.parse(argc, argv);

    mlog::setLevel(mlog::Level::INFO);

    std::unique_ptr<common::ExtendedXmlDoc> configDoc;
    if (config.defined()) {
        configDoc.reset(new common::ExtendedXmlDoc(config));
    } else {
        configDoc = common::loadDefaultConfig();
    }
    INFO() << "Using config: " << configDoc->sourcePaths()[0];
    g_dbHolder.reset(new common::PoolHolder(*configDoc, "core", "dataset_explorer"));

    INFO() << "Starting " << APP_NAME;

    yacare::setErrorReporter(errorReporter);
    yacare::run(yacare::RunSettings{.useSystemDefaultLocale = true});

    INFO() << "Shutting down " << APP_NAME;
    return EXIT_SUCCESS;
} catch (const maps::Exception& e) {
    ERROR() << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    ERROR() << e.what();
    return EXIT_FAILURE;
} catch (...) {
    ERROR() << "Caught unknown exception";
    return EXIT_FAILURE;
}
