#pragma once

#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/http/include/http.h>

#include <maps/libs/common/include/retry.h>
#include <maps/libs/geolib/include/multipolygon.h>

#include <mapreduce/yt/interface/client.h>

#include <contrib/libs/geos/include/geos/geom/Geometry.h>
#include <contrib/libs/geos/include/geos/geom/MultiPolygon.h>
#include <contrib/libs/geos/include/geos/operation/union/CascadedUnion.h>

#include <contrib/libs/gdal/gcore/gdal.h>
#include <contrib/libs/gdal/ogr/ogrsf_frmts/ogrsf_frmts.h>


#include <set>
#include <map>
#include <vector>
#include <optional>

namespace maps::wiki::autocart::pipeline {

struct Release {
    uint64_t issueId;
    uint64_t releaseId;

    NYT::TNode toYTNode() const;
    void toYTNode(NYT::TNode& node) const;
    static Release fromYTNode(const NYT::TNode& node);

    bool operator<(const Release& that) const;
    bool operator==(const Release& that) const;
};

std::ostream& operator<<(std::ostream& os, const Release& release);


struct ReleaseGeometry {
    NYT::TNode toYTNode() const;
    void toYTNode(NYT::TNode& node) const;
    static ReleaseGeometry fromYTNode(const NYT::TNode& node);

    int zmin;
    int zmax;
    geolib3::MultiPolygon2 mercatorGeom;

    bool operator==(const ReleaseGeometry& that) const;
};

using ReleaseGeometries = std::vector<ReleaseGeometry>;

struct ReleaseAttrs {
    ReleaseGeometries geometries;
    chrono::TimePoint date;
};

using ReleaseToAttrs = std::map<Release, ReleaseAttrs>;

struct ReleasesCoverage {
    NYT::TNode toYTNode() const;
    void toYTNode(NYT::TNode& node) const;
    static ReleasesCoverage fromYTNode(const NYT::TNode& node);

    int z;
    Release lastRelease;
    geolib3::MultiPolygon2 mercatorGeom;
};

class FactoryServiceClient {
public:

    FactoryServiceClient();

    virtual ~FactoryServiceClient() = default;

    virtual std::set<Release> getAllReleases() = 0;

    // Throw exception if there is no release with given releaseId
    // or satellite factory is not available
    virtual ReleaseGeometries loadReleaseGeometries(uint64_t releaseId) = 0;
protected:
    http::Response performRequest(http::Method method, const http::URL& url);

    http::Client client_;
    maps::common::RetryPolicy retryPolicy_;

};

class XMLFactoryServiceClient : public FactoryServiceClient {
public:
    inline static const std::string FACTORY_URL = "http://core-factory-back.maps.yandex.net";

    XMLFactoryServiceClient(const std::string& url = FACTORY_URL);

    std::set<Release> getAllReleases() override;

    ReleaseGeometries loadReleaseGeometries(uint64_t releaseId) override;

private:
    const http::URL factoryUrl_;
};


class ProtoFactoryServiceClient : public FactoryServiceClient {
public:
    inline static const std::string FACTORY_URL = "http://core-factory-backend.maps.yandex.net";

    ProtoFactoryServiceClient(const std::string& url = FACTORY_URL);

    std::set<Release> getAllReleases() override;

    ReleaseGeometries loadReleaseGeometries(uint64_t releaseId) override;

private:
    const http::URL factoryUrl_;
};


// Return std::nullopt if there is no release with given issueId
std::optional<Release> getReleaseByIssueId(
    FactoryServiceClient& client, uint64_t issueId);


// Return all releases that have greater issueId
// Return empty set if there is no release with given releaseId
std::set<Release> getAllNextReleases(
    FactoryServiceClient& client, uint64_t releaseId);

std::map<Release, ReleaseGeometries> loadAllNextReleasesGeometries(
    FactoryServiceClient& client, uint64_t releaseId);

std::map<Release, ReleaseGeometries> loadAllReleasesGeometries(FactoryServiceClient& client);

geolib3::MultiPolygon2 getReleasesCoverageAtZoom(
    const std::map<Release, ReleaseAttrs>& releaseToAttrs, int zoom);

std::pair<ReleaseToAttrs, ReleaseToAttrs> splitNotReadyReleases(
    const ReleaseToAttrs& releaseToAttrs,
    long int hoursGap = 24 * 7 * 3 /* 3 weeks */);

} // namespace maps::wiki::autocart::pipeline
