#include "response.h"

#include <maps/libs/chrono/include/time_point.h>
#include <yandex/maps/proto/firmware_updater/storage/branch.pb.h>
#include <yandex/maps/proto/firmware_updater/storage/firmware.pb.h>
#include <yandex/maps/proto/firmware_updater/storage/rollout.pb.h>
#include <yandex/maps/proto/firmware_updater/storage/upload.pb.h>

#include <chrono>

namespace maps::fw_updater::storage {

namespace proto = yandex::maps::proto::firmware_updater::storage;

namespace {

constexpr auto PROTOBUF_MEDIA_TYPE = "application/x-protobuf";

int64_t toMillis(maps::chrono::TimePoint timePoint)
{
    return std::chrono::duration_cast<std::chrono::milliseconds>(
        timePoint.time_since_epoch()).count();
}

proto::UploadPart toProto(const db::FirmwareUploadPart& uploadPart)
{
    proto::UploadPart result;
    result.set_upload_id(std::to_string(uploadPart.uploadId()).c_str());
    result.set_part_id(std::to_string(uploadPart.id()).c_str());
    result.set_content_size(uploadPart.contentSize());
    result.set_content_etag(uploadPart.contentEtag().c_str());

    return result;
}

proto::Upload toProto(const db::FirmwareUpload& upload,
                      const db::FirmwareUploadParts& uploadParts)
{
    proto::Upload result;
    result.set_id(std::to_string(upload.id()).c_str());
    result.set_hardware(upload.hardwareId().c_str());
    result.set_slot(toString(upload.slot()).data());
    result.set_version(upload.version().c_str());
    result.set_created_at(toMillis(upload.createdAt()));
    result.set_created_by(upload.createdBy().c_str());

    for (const auto& part : uploadParts) {
        auto protoPart = toProto(part);
        result.add_parts()->Swap(&protoPart);
    }

    return result;
}

proto::Firmware toProto(const db::Firmware& firmware)
{
    proto::Firmware result;
    result.set_hardware(firmware.hardwareId().c_str());
    result.set_slot(toString(firmware.slot()).data());
    result.set_version(firmware.version().c_str());
    result.set_version_seq(firmware.versionSeq());
    result.set_download_url(firmware.url().c_str());
    result.set_content_md5(firmware.md5().c_str());
    result.set_content_size(firmware.size());

    return result;
}

proto::DeviceBranch toProto(const db::DeviceBranch& deviceBranch)
{
    proto::DeviceBranch result;
    result.set_hardware(deviceBranch.hardwareId().c_str());
    result.set_deviceid(deviceBranch.deviceId().c_str());
    result.set_branch(deviceBranch.branch().c_str());

    return result;
}

proto::Rollout toProto(const db::RolloutHistory& rolloutHistory,
                       const db::Rollout& rollout,
                       const db::Firmware& firmware)
{
    proto::Rollout result;
    result.set_id(std::to_string(rollout.id()).c_str());

    auto pbFirmware = toProto(firmware);
    result.mutable_firmware()->Swap(&pbFirmware);
    result.set_branch(rollout.branch().c_str());
    result.set_percent(rolloutHistory.percent());
    result.set_status(toString(rolloutHistory.status()).data());
    result.set_created_at(toMillis(rolloutHistory.createdAt()));
    result.set_created_by(rolloutHistory.createdBy().c_str());

    return result;
}

} // namespace

void makeFirmwareUploadPartResponse(
    yacare::Response& response,
    const db::FirmwareUploadPart& firmwareUploadPart)
{
    auto pb = toProto(firmwareUploadPart);
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeFirmwareUploadResponse(
    yacare::Response& response,
    const db::FirmwareUpload& firmwareUpload,
    const db::FirmwareUploadParts& firmwareUploadParts)
{
    auto pb = toProto(firmwareUpload, firmwareUploadParts);
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeFirmwareUploadsListResponse(
    yacare::Response& response,
    const std::vector<FirmwareUploadWithParts>& firmwareUploadsWithParts)
{
    proto::UploadsList pb;

    for (const auto& firmwareUploadWithParts : firmwareUploadsWithParts) {
        auto upload = toProto(firmwareUploadWithParts.upload,
                              firmwareUploadWithParts.parts);
        pb.add_uploads()->Swap(&upload);
    }
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeFirmwareResponse(
    yacare::Response& response,
    const db::Firmware& firmware)
{
    auto pb = toProto(firmware);
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeFirmwaresListResponse(
    yacare::Response& response,
    const db::Firmwares& firmwares)
{
    proto::FirmwaresList pb;

    for (const auto& firmware : firmwares) {
        auto pbFirmware = toProto(firmware);
        pb.add_firmwares()->Swap(&pbFirmware);
    }
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeDeviceBranchResponse(
    yacare::Response& response,
    const db::DeviceBranch& deviceBranch)
{
    auto pb = toProto(deviceBranch);
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeDeviceBranchesListResponse(
    yacare::Response& response,
    const db::DeviceBranches& deviceBranches)
{
    proto::DeviceBranchesList pb;

    for (const auto& deviceBranch : deviceBranches) {
        auto pbDeviceBranch = toProto(deviceBranch);
        pb.add_device_branches()->Swap(&pbDeviceBranch);
    }
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeRolloutResponse(
    yacare::Response& response,
    const db::RolloutHistory& rolloutHistory,
    const db::Rollout& rollout,
    const db::Firmware& firmware)
{
    auto pb = toProto(rolloutHistory, rollout, firmware);
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

void makeRolloutsListResponse(
    yacare::Response& response,
    const db::RolloutHistories& rolloutHistories,
    const std::unordered_map<db::TId, db::Rollout>& idToRollout,
    const std::unordered_map<db::TId, db::Firmware>& idToFirmware)
{
    proto::RolloutsList pb;
    for (const auto& rolloutHistory : rolloutHistories) {
        const auto& rollout = idToRollout.at(rolloutHistory.rolloutId());
        const auto& firmware = idToFirmware.at(rollout.firmwareId());
        auto pbRollout = toProto(rolloutHistory, rollout, firmware);
        pb.add_rollouts()->Swap(&pbRollout);
    }
    Y_PROTOBUF_SUPPRESS_NODISCARD pb.SerializeToOstream(&response);
    response["Content-Type"] = PROTOBUF_MEDIA_TYPE;
}

} //namespace maps::fw_updater::storage
