// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include <set>

#include <fmt/ranges.h>

#include <mtx/responses/common.hpp>
#include <mtx/responses/register.hpp>
#include <mtx/responses/version.hpp>
#include <mtx/responses/well-known.hpp>
#include <mtxclient/http/client.hpp>

#include "Logging.h"
#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "RegisterPage.h"
#include "ui/UIA.h"

RegisterPage::RegisterPage(QObject *parent)
  : QObject(parent)
{
    connect(this, &RegisterPage::registerOk, this, [] { MainWindow::instance()->showChatPage(); });
}

void
RegisterPage::setError(const QString &err)
{
    registrationError_ = err;
    emit errorChanged();
    registering_ = false;
    emit registeringChanged();
}
void
RegisterPage::setHsError(const QString &err)
{
    hsError_ = err;
    emit hsErrorChanged();
    lookingUpHs_ = false;
    emit lookingUpHsChanged();
}

QString
RegisterPage::initialDeviceName() const
{
    return QString::fromStdString(LoginPage::initialDeviceName_());
}

void
RegisterPage::setServer(const QString &server)
{
    if (server == lastServer)
        return;

    lastServer = server;

    http::client()->set_server(server.toStdString());
    http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());

    hsError_.clear();
    emit hsErrorChanged();
    supported_   = false;
    lookingUpHs_ = true;
    emit lookingUpHsChanged();

    http::client()->well_known(
      [this, prevServer = server](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
          // server changed in between
          if (lastServer != prevServer)
              return;

          if (err) {
              if (err->status_code == 404) {
                  nhlog::net()->info("Autodiscovery: No .well-known.");
                  // Check that the homeserver can be reached
                  versionsCheck();
                  return;
              }

              if (!err->parse_error.empty()) {
                  setHsError(tr("Autodiscovery failed. Received malformed response."));
                  nhlog::net()->error("Autodiscovery failed. Received malformed response. {}",
                                      err->parse_error);
                  emit hsErrorChanged();
                  return;
              }

              setHsError(tr("Autodiscovery failed. Unknown error when requesting .well-known."));
              nhlog::net()->error("Autodiscovery failed. Unknown error when "
                                  "requesting .well-known. {}",
                                  *err);
              return;
          }

          nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
          http::client()->set_server(res.homeserver.base_url);
          emit hsErrorChanged();
          // Check that the homeserver can be reached
          versionsCheck();
      });
}

void
RegisterPage::versionsCheck()
{
    // Make a request to /_matrix/client/versions to check the address
    // given is a Matrix homeserver.
    http::client()->versions([this](const mtx::responses::Versions &versions,
                                    mtx::http::RequestErr err) {
        if (err) {
            if (err->status_code == 404) {
                setHsError(
                  tr("The required endpoints were not found. Possibly not a Matrix server."));
                emit hsErrorChanged();
                return;
            }

            if (!err->parse_error.empty()) {
                setHsError(
                  tr("Received malformed response. Make sure the homeserver domain is valid."));
                emit hsErrorChanged();
                return;
            }

            setHsError(tr("An unknown error occured. Make sure the homeserver domain is valid."));
            emit hsErrorChanged();
            return;
        }

        if (std::find_if(
              versions.versions.cbegin(), versions.versions.cend(), [](const std::string &v) {
                  static const std::set<std::string_view, std::less<>> supported{
                    "v1.1",
                    "v1.2",
                    "v1.3",
                    "v1.4",
                    "v1.5",
                    "v1.6",
                    "v1.7",
                    "v1.8",
                    "v1.9",
                    "v1.10",
                  };
                  return supported.count(v) != 0;
              }) == versions.versions.cend()) {
            emit setHsError(
              tr("The selected server does not support a version of the Matrix protocol that "
                 "this client understands (%1 to %2). You can't register.")
                .arg(u"v1.1", u"v1.10"));
            emit hsErrorChanged();
            return;
        }

        http::client()->registration([this](const mtx::responses::Register &,
                                            mtx::http::RequestErr e) {
            nhlog::net()->debug("Registration check: {}", e);

            if (!e) {
                setHsError(tr("Server does not support querying registration flows!"));
                emit hsErrorChanged();
                return;
            }
            if (e->status_code != 401) {
                setHsError(tr("Server does not support registration."));
                emit hsErrorChanged();
                return;
            }

            for (const auto &f : e->matrix_error.unauthorized.flows)
                nhlog::ui()->debug("Registration flows for server: {}", fmt::join(f.stages, ", "));

            supported_   = true;
            lookingUpHs_ = false;
            emit lookingUpHsChanged();
        });
    });
}

void
RegisterPage::checkUsername(const QString &name)
{
    usernameAvailable_ = usernameUnavailable_ = false;
    usernameError_.clear();
    lookingUpUsername_ = true;
    emit lookingUpUsernameChanged();

    http::client()->register_username_available(
      name.toStdString(),
      [this](const mtx::responses::Available &available, mtx::http::RequestErr e) {
          if (e) {
              if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_INVALID_USERNAME) {
                  usernameError_ = tr("Invalid username.");
              } else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_USER_IN_USE) {
                  usernameError_ = tr("Name already in use.");
              } else if (e->matrix_error.errcode == mtx::errors::ErrorCode::M_EXCLUSIVE) {
                  usernameError_ = tr("Part of the reserved namespace.");
              } else {
              }

              usernameAvailable_   = false;
              usernameUnavailable_ = true;
          } else {
              usernameAvailable_   = available.available;
              usernameUnavailable_ = !available.available;
          }
          lookingUpUsername_ = false;
          emit lookingUpUsernameChanged();
      });
}

void
RegisterPage::startRegistration(const QString &username,
                                const QString &password,
                                const QString &devicename)
{
    // These inputs should still be alright, but check just in case
    if (!username.isEmpty() && !password.isEmpty() && usernameAvailable_ && supported_) {
        registrationError_.clear();
        emit errorChanged();
        registering_ = true;
        emit registeringChanged();

        connect(UIA::instance(), &UIA::error, this, [this](const QString &msg) {
            setError(msg);
            disconnect(UIA::instance(), &UIA::error, this, nullptr);
        });
        http::client()->registration(
          username.toStdString(),
          password.toStdString(),
          ::UIA::instance()->genericHandler(QStringLiteral("Registration")),
          [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
              registering_ = false;
              emit registeringChanged();

              if (!err) {
                  http::client()->set_user(res.user_id);
                  http::client()->set_access_token(res.access_token);
                  emit registerOk();
                  disconnect(UIA::instance(), &UIA::error, this, nullptr);
                  return;
              }

              // The server requires registration flows.
              if (err->status_code == 401 && err->matrix_error.unauthorized.flows.empty()) {
                  nhlog::net()->warn("failed to retrieve registration flows: {}", *err);
                  setError(QString::fromStdString(err->matrix_error.error));
                  disconnect(UIA::instance(), &UIA::error, this, nullptr);
                  return;
              }

              nhlog::net()->error("failed to register: {}", *err);

              setError(QString::fromStdString(err->matrix_error.error));
              disconnect(UIA::instance(), &UIA::error, this, nullptr);
          },
          devicename.isEmpty() ? LoginPage::initialDeviceName_() : devicename.toStdString());
    }
}

#include "moc_RegisterPage.cpp"
