#pragma once

#include "common.h"

#include <maps/libs/geolib/include/conversion.h>
#include <maps/libs/geolib/include/polyline.h>
#include <maps/libs/sql_chemistry/include/gateway_access.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/txn_id.h>

#include <vector>

namespace maps::mrc::db {

class Ride {
public:
    Ride(std::string userId,
         chrono::TimePoint startTime,
         chrono::TimePoint endTime,
         std::string sourceId,
         const geolib3::Polyline2& geodeticTrack,
         double distanceInMeters,
         size_t photos,
         chrono::TimePoint uploadStartTime,
         chrono::TimePoint uploadEndTime,
         bool isDeleted)
        : Ride()
    {
        setUserId(std::move(userId));
        setEndTime(endTime);
        setStartTime(startTime);
        setSourceId(std::move(sourceId));
        setGeodeticTrack(geodeticTrack);
        setDistanceInMeters(distanceInMeters);
        setPhotos(photos);
        setUploadEndTime(uploadEndTime);
        setUploadStartTime(uploadStartTime);
        setIsDeleted(isDeleted);
    }

    TId rideId() const { return rideId_; }

    const std::string& userId() const { return userId_; }

    chrono::TimePoint startTime() const { return startTime_; }

    chrono::TimePoint endTime() const { return endTime_; }

    const std::string& sourceId() const { return sourceId_; }

    const geolib3::Polyline2& mercatorTrack() const { return mercatorTrack_; }

    geolib3::Polyline2 geodeticTrack() const
    {
        return geolib3::convertMercatorToGeodetic(mercatorTrack());
    }

    double distanceInMeters() const { return distanceInMeters_; }

    size_t photos() const { return photos_; }

    chrono::TimePoint uploadStartTime() const { return uploadStartTime_; }

    chrono::TimePoint uploadEndTime() const { return uploadEndTime_; }

    bool isDeleted() const { return isDeleted_; }

    bool hasShowAuthorship() const { return showAuthorship_.has_value(); }

    bool showAuthorship() const
    {
        REQUIRE(hasShowAuthorship(), "showAuthorship should not be empty");
        return showAuthorship_.value();
    }

    RideStatus status() const
    {
        return status_.value_or(RideStatus::Pending);
    }

    const std::optional<std::string>& clientId() const
    {
        return clientId_;
    }

    const std::optional<size_t>& publishedPhotos() const
    {
        return publishedPhotos_;
    }

    TId txnId() const { return txnId_; }

    Ride& setUserId(std::string userId)
    {
        userId_ = std::move(userId);
        return *this;
    }

    Ride& setStartTime(chrono::TimePoint startTime)
    {
        REQUIRE(startTime <= endTime_, "invalid time interval");
        startTime_ = startTime;
        return *this;
    }

    Ride& setEndTime(chrono::TimePoint endTime)
    {
        REQUIRE(startTime_ <= endTime, "invalid time interval");
        endTime_ = endTime;
        return *this;
    }

    Ride& setTimes(chrono::TimePoint startTime, chrono::TimePoint endTime)
    {
        REQUIRE(startTime <= endTime, "invalid time interval");
        startTime_ = startTime;
        endTime_ = endTime;
        return *this;
    }

    Ride& setSourceId(std::string sourceId)
    {
        sourceId_ = std::move(sourceId);
        return *this;
    }

    Ride& setMercatorTrack(geolib3::Polyline2 mercatorTrack)
    {
        mercatorTrack_ = std::move(mercatorTrack);
        return *this;
    }

    Ride& setGeodeticTrack(const geolib3::Polyline2& geodeticTrack)
    {
        return setMercatorTrack(
            geolib3::convertGeodeticToMercator(geodeticTrack));
    }

    Ride& setDistanceInMeters(double distanceInMeters)
    {
        distanceInMeters_ = distanceInMeters;
        return *this;
    }

    Ride& setPhotos(size_t photos)
    {
        photos_ = photos;
        return *this;
    }

    Ride& setUploadStartTime(chrono::TimePoint uploadStartTime)
    {
        REQUIRE(uploadStartTime <= uploadEndTime_,
                "invalid uploading time interval");
        uploadStartTime_ = uploadStartTime;
        return *this;
    }

    Ride& setUploadEndTime(chrono::TimePoint uploadEndTime)
    {
        REQUIRE(uploadStartTime_ <= uploadEndTime,
                "invalid uploading time interval");
        uploadEndTime_ = uploadEndTime;
        return *this;
    }

    Ride& setUploadTimes(chrono::TimePoint uploadStartTime,
                         chrono::TimePoint uploadEndTime)
    {
        REQUIRE(uploadStartTime <= uploadEndTime,
                "invalid uploading time interval");
        uploadStartTime_ = uploadStartTime;
        uploadEndTime_ = uploadEndTime;
        return *this;
    }

    Ride& setIsDeleted(bool isDeleted)
    {
        isDeleted_ = isDeleted;
        return *this;
    }

    Ride& setShowAuthorship(bool showAuthorship)
    {
        showAuthorship_ = showAuthorship;
        return *this;
    }

    Ride& setStatus(RideStatus status)
    {
        status_ = status;
        return *this;
    }

    Ride& setClientId(std::string clientId)
    {
        clientId_ = std::move(clientId);
        return *this;
    }

    Ride& resetClientId()
    {
        clientId_ = std::nullopt;
        return *this;
    }

    Ride& setPublishedPhotos(size_t publishedPhotos)
    {
        publishedPhotos_ = publishedPhotos;
        return *this;
    }

    Ride& resetPublishedPhotos()
    {
        publishedPhotos_ = std::nullopt;
        return *this;
    }

    Ride& copyContentFrom(const Ride& that)
    {
        auto rideId = rideId_;
        auto xmin = xmin_;
        auto txnId = txnId_;
        *this = that;
        rideId_ = rideId;
        xmin_ = xmin;
        txnId_ = txnId;
        return *this;
    }

private:
    friend class sql_chemistry::GatewayAccess<Ride>;
    friend class TxnIdAccess<Ride>;

    Ride() = default;

    Ride& setTxnId(TId txnId)
    {
        txnId_ = txnId;
        return *this;
    }

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.rideId_,
                        t.userId_,
                        t.startTime_,
                        t.endTime_,
                        t.sourceId_,
                        t.mercatorTrack_,
                        t.distanceInMeters_,
                        t.photos_,
                        t.uploadStartTime_,
                        t.uploadEndTime_,
                        t.isDeleted_,
                        t.showAuthorship_,
                        t.status_,
                        t.clientId_,
                        t.publishedPhotos_,
                        t.xmin_,
                        t.txnId_);
    }

    TId rideId_;
    std::string userId_;
    chrono::TimePoint startTime_;
    chrono::TimePoint endTime_;
    std::string sourceId_;
    geolib3::Polyline2 mercatorTrack_;
    double distanceInMeters_;
    size_t photos_;
    chrono::TimePoint uploadStartTime_;
    chrono::TimePoint uploadEndTime_;
    bool isDeleted_;
    std::optional<bool> showAuthorship_;
    std::optional<RideStatus> status_;
    std::optional<std::string> clientId_;
    std::optional<size_t> publishedPhotos_;
    uint64_t xmin_;
    TId txnId_;

public:
    auto introspect() const { return introspect(*this); }
};

using Rides = std::vector<Ride>;


class RideLiteView {
public:
    TId rideId() const { return rideId_; }

    const std::string& userId() const { return userId_; }

    chrono::TimePoint startTime() const { return startTime_; }

    chrono::TimePoint endTime() const { return endTime_; }

    double distanceInMeters() const { return distanceInMeters_; }

    size_t photos() const { return photos_; }

    const std::optional<size_t>& publishedPhotos() const
    {
        return publishedPhotos_;
    }

    bool isDeleted() const { return isDeleted_; }

private:
    friend class sql_chemistry::GatewayAccess<RideLiteView>;

    RideLiteView() = default;

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.rideId_,
                        t.userId_,
                        t.startTime_,
                        t.endTime_,
                        t.distanceInMeters_,
                        t.photos_,
                        t.publishedPhotos_,
                        t.isDeleted_);
    }

    TId rideId_;
    std::string userId_;
    chrono::TimePoint startTime_;
    chrono::TimePoint endTime_;
    double distanceInMeters_;
    size_t photos_;
    std::optional<size_t> publishedPhotos_;
    bool isDeleted_;

public:
    auto introspect() const { return introspect(*this); }
};

using RideLiteViews = std::vector<RideLiteView>;


class DeletedInterval {
public:
    DeletedInterval(std::string userId,
                    std::string sourceId,
                    std::optional<std::string> clientRideId,
                    chrono::TimePoint startedAt,
                    chrono::TimePoint endedAt)
        : id_{}
        , userId_{std::move(userId)}
        , sourceId_{std::move(sourceId)}
        , startedAt_{startedAt}
        , endedAt_{endedAt}
        , createdAt_{chrono::TimePoint::clock::now()}
        , clientRideId_{std::move(clientRideId)}
    {
        REQUIRE(startedAt_ <= endedAt_, "invalid time interval");
    }

    TId id() const { return id_; }

    const std::string& userId() const { return userId_; }

    const std::string& sourceId() const { return sourceId_; }

    chrono::TimePoint startedAt() const { return startedAt_; }

    chrono::TimePoint endedAt() const { return endedAt_; }

    chrono::TimePoint createdAt() const { return createdAt_; }

    const std::optional<std::string>& clientRideId() const
    {
        return clientRideId_;
    }

private:
    friend class sql_chemistry::GatewayAccess<DeletedInterval>;

    DeletedInterval() = default;

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.id_,
                        t.userId_,
                        t.sourceId_,
                        t.startedAt_,
                        t.endedAt_,
                        t.createdAt_,
                        t.clientRideId_);
    }

    TId id_;
    std::string userId_;
    std::string sourceId_;
    chrono::TimePoint startedAt_;
    chrono::TimePoint endedAt_;
    chrono::TimePoint createdAt_;
    std::optional<std::string> clientRideId_;

public:
    auto introspect() const { return introspect(*this); }
};

using DeletedIntervals = std::vector<DeletedInterval>;

class RideHypothesis {
public:
    RideHypothesis(TId rideId, TId hypothesisId)
        : rideId_{rideId}, hypothesisId_{hypothesisId}
    {
    }

    TId rideId() const { return rideId_; }
    TId hypothesisId() const { return hypothesisId_; }

private:
    friend class sql_chemistry::GatewayAccess<RideHypothesis>;

    RideHypothesis() = default;

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.rideId_, t.hypothesisId_);
    }

    TId rideId_;
    TId hypothesisId_;

public:
    auto introspect() const { return introspect(*this); }
};

using RideHypotheses = std::vector<RideHypothesis>;

}  // namespace maps::mrc::db
