#include "mediaparticlegenerator.h"
#include "networkmanager.h"
#include "store.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>

MediaParticleGenerator::MediaParticleGenerator(Store *store, QObject *parent)
    : QObject{parent}, m_store{store}, m_qnam{new QNetworkAccessManager{this}}
{
}

bool MediaParticleGenerator::isGenerating() const
{
  return m_isGenerating;
}

void MediaParticleGenerator::generate(const QUrl &url, const QString &mimeType, const QString &networkId, qint64 durationMs, const QString &streamId)
{
  if (m_isGenerating)
  {
    qWarning() << "Already generating media particle, ignoring request";
    return;
  }
  if (!url.isValid())
  {
    emit errorOccurred("Invalid URL");
    return;
  }

  assert(!mimeType.isEmpty() && "mimeType should never be empty here");
  assert(!networkId.isEmpty() && "networkId should never be empty here");

  QFile *file = new QFile{url.toLocalFile()};
  assert(file->exists() && "File should exist at this point");
  assert(file->size() > 0 && "File should have non-zero size at this point");

  cleanup();
  setGenerating(true);

  m_uploadInfo = new UploadInfo{};
  m_uploadInfo->networkId = networkId;
  m_uploadInfo->streamId = streamId;
  m_uploadInfo->mimeType = mimeType;
  m_uploadInfo->durationMs = durationMs;
  m_uploadInfo->file = file;
  m_uploadInfo->bytes = file->size();

  // 1. get presigned url
  QJsonObject body;
  body["network_id"] = m_uploadInfo->networkId;
  body["name"] = url.fileName();
  body["content_type"] = m_uploadInfo->mimeType;
  body["content_length"] = m_uploadInfo->bytes;
  QNetworkReply *reply = NetworkManager::instance().post("/depot/upload", QJsonDocument(body));
  connect(reply, &QNetworkReply::finished, this, [this, reply]() {
    reply->deleteLater();

    if (reply->error() != QNetworkReply::NoError)
    {
      QByteArray body = reply->readAll();
      QJsonDocument doc = QJsonDocument::fromJson(body);
      QString message = doc.isObject() ? doc.object().value("message").toString() : QString();
      if (message.isEmpty())
      {
        message = reply->errorString();
      }
      qWarning() << message;
      cleanup();
      emit errorOccurred("Failed to upload media");
      return;
    }

    QJsonDocument doc = QJsonDocument::fromJson(reply->readAll());
    onUploadUrlReceived(doc);
  });
}

void MediaParticleGenerator::cleanup()
{
  if (m_uploadInfo)
  {
    if (m_uploadInfo->file)
    {
      m_uploadInfo->file->close();
      delete m_uploadInfo->file;
    }
    delete m_uploadInfo;
    m_uploadInfo = nullptr;
  }

  setGenerating(false);
}

void MediaParticleGenerator::onUploadUrlReceived(const QJsonDocument &response)
{
  QJsonObject obj = response.object();
  m_uploadInfo->uploadUrl = obj["upload_url"].toString();
  QJsonObject uploadHeaders = obj["upload_headers"].toObject();
  m_uploadInfo->objectId = obj["object_id"].toString();

  if (!m_uploadInfo->file->open(QIODevice::ReadOnly))
  {
    cleanup();
    emit errorOccurred("Failed to open media file");
    return;
  }

  QNetworkRequest request = QNetworkRequest(QUrl(m_uploadInfo->uploadUrl));
  for (auto it = uploadHeaders.constBegin(); it != uploadHeaders.constEnd(); ++it)
  {
    request.setRawHeader(it.key().toUtf8(), it.value().toString().toUtf8());
  }
  QNetworkReply *reply = m_qnam->put(request, m_uploadInfo->file);
  connect(reply, &QNetworkReply::finished, this, [this, reply]() {
    reply->deleteLater();

    if (reply->error() != QNetworkReply::NoError)
    {
      qWarning() << "Failed to upload media: " << reply->errorString();
      cleanup();
      emit errorOccurred("Failed to upload media");
      return;
    }

    onUploadComplete();
  });
}

void MediaParticleGenerator::setGenerating(bool generating)
{
  if (m_isGenerating != generating)
  {
    m_isGenerating = generating;
    emit isGeneratingChanged();
  }
}

void MediaParticleGenerator::onUploadComplete()
{
  auto op = m_store->createParticle(m_uploadInfo->streamId, "media", {
    {"object_id", m_uploadInfo->objectId},
    {"duration_ms", m_uploadInfo->durationMs},
    {"mime_type", m_uploadInfo->mimeType}
  });

  connect(op, &Operation::success, this, &MediaParticleGenerator::complete);
  connect(op, &Operation::success, this, &MediaParticleGenerator::cleanup);
}
