#include "authmanager.h"
#include "networkmanager.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QSettings>

namespace
{
constexpr char key[] = "network/session_token";
}

AuthManager::AuthManager(QObject *parent)
    : QObject{parent}, m_sessionData(nullptr), m_settings(new QSettings(this)), m_isSignedIn(false), m_lastEmail(""),
      m_sessionToken("")
{
  connect(&NetworkManager::instance(), &NetworkManager::unauthorizedDetected, this, [this]() {
    if (!m_isSignedIn)
    {
      return;
    }

    setIsSignedIn(false);
    if (m_sessionData)
    {
      delete m_sessionData;
      m_sessionData = nullptr;
    }
    setSessionToken("");
  });
}

void AuthManager::tryRestoreSession()
{
  QString storageSessionToken = m_settings->value(key).toString();
  if (storageSessionToken.isEmpty())
  {
    return;
  }
  qDebug() << "Storage session token is: " << storageSessionToken;

  // set it here, and unset if it fails...network manager needs to inject into the request
  setSessionToken(storageSessionToken);

  QNetworkReply *reply = NetworkManager::instance().get("/auth/me");
  connect(reply, &QNetworkReply::finished, this, [=]() {
    reply->deleteLater();

    if (reply->error() != QNetworkReply::NoError)
    {
      qDebug() << "Saved session token is invalid";
      setSessionToken("");
      return;
    }

    qDebug() << "Saved session token is valid";

    QByteArray responseData = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
    if (jsonDoc.isNull())
    {
      qWarning() << "Failed to create a JSON doc.";
      setSessionToken("");
      return;
    }

    QJsonObject jsonObj = jsonDoc.object();

    if (!m_sessionData)
    {
      m_sessionData = new SessionData{};
    }
    m_sessionData->id = jsonObj["id"].toString();
    m_sessionData->email = jsonObj["email"].toString();
    m_sessionData->emailPrefix = jsonObj["email_prefix"].toString();

    setSessionToken(storageSessionToken);

    setIsSignedIn(true);
  });
}

void AuthManager::setIsSignedIn(bool newValue)
{
  if (m_isSignedIn == newValue)
  {
    return;
  }

  m_isSignedIn = newValue;
  emit isSignedInChanged();

  if (m_isSignedIn)
  {
    emit signedIn();
  }
  else
  {
    emit signedOut();
  }
}

bool AuthManager::isSignedIn() const
{
  return m_isSignedIn;
}

SessionData *AuthManager::sessionData() const
{
  return m_sessionData;
}

void AuthManager::requestSignInCode(const QString &email)
{
  if (isSignedIn())
  {
    qWarning() << "Must sign out first";
    return;
  }

  QJsonDocument body;
  QJsonObject object;
  object["email"] = email;
  body.setObject(object);
  QNetworkReply *reply = NetworkManager::instance().post("/auth/request-code", body);

  connect(reply, &QNetworkReply::finished, this, [=]() {
    reply->deleteLater();
    if (reply->error() != QNetworkReply::NoError)
    {
      emit errorOccurred("Unable to send code. Please try again.");
      qDebug() << "Error in emailing auth code: " << reply->errorString();
      return;
    }

    qDebug() << "Code successfully sent";
    m_lastEmail = email;
    emit codeSentToEmail(email);
  });
}

void AuthManager::signIn(const QString &code)
{
  if (isSignedIn())
  {
    qWarning() << "Must sign out first";
    return;
  }
  assert(!m_lastEmail.isEmpty());

  QJsonDocument body;
  QJsonObject object;
  object["email"] = m_lastEmail;
  object["code"] = code;
  body.setObject(object);
  QNetworkReply *reply = NetworkManager::instance().post("/auth/sign-in", body);

  connect(reply, &QNetworkReply::finished, this, [=]() {
    reply->deleteLater();
    if (reply->error() != QNetworkReply::NoError)
    {
      emit errorOccurred("Unable to verify code. Please try again.");
      qDebug() << "Error in verify code: " << reply->errorString();
      return;
    }

    qDebug() << "Session successfully created";
    QByteArray responseData = reply->readAll();
    QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
    if (jsonDoc.isNull())
    {
      qWarning() << "Failed to create a JSON doc.";
      emit errorOccurred("Unable to verify code");
      return;
    }

    QJsonObject jsonObj = jsonDoc.object();

    if (!m_sessionData)
    {
      m_sessionData = new SessionData{};
    }
    QJsonObject human = jsonObj["human"].toObject();
    m_sessionData->id = human["id"].toString();
    m_sessionData->email = human["email"].toString();
    m_sessionData->emailPrefix = human["email_prefix"].toString();

    setSessionToken(jsonObj["token"].toString());

    setIsSignedIn(true);
  });
}

void AuthManager::signOut()
{
  if (!isSignedIn())
  {
    qWarning() << "Not signed in, skipping sign out";
    return;
  }

  setIsSignedIn(false);
  if (m_sessionData)
  {
    delete m_sessionData;
    m_sessionData = nullptr;
  }
  setSessionToken("");

  QNetworkReply *reply = NetworkManager::instance().post("/auth/sign-out", QJsonDocument());
  connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
}

void AuthManager::setSessionToken(const QString &newValue)
{
  if (newValue.isEmpty())
  {
    m_settings->remove(key);
  }
  else
  {
    m_settings->setValue(key, newValue);
  }

  m_sessionToken = newValue;
  NetworkManager::instance().setSessionToken(newValue);
}
