// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0

#include "authwidget.h"

#include "copilotclient.h"
#include "copilotsettings.h"
#include "copilottr.h"

#include <utils/guardedcallback.h>
#include <utils/layoutbuilder.h>
#include <utils/stringutils.h>

#include <languageclient/languageclientmanager.h>

#include <QDesktopServices>
#include <QMessageBox>

using namespace LanguageClient;
using namespace Copilot::Internal;
using namespace Utils;

namespace Copilot {

AuthWidget::AuthWidget(QWidget *parent)
    : QWidget(parent)
{
    using namespace Layouting;

    m_button = new QPushButton(Tr::tr("Sign In"));
    m_button->setEnabled(false);
    m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Small);
    m_progressIndicator->setVisible(false);
    m_statusLabel = new QLabel();
    m_statusLabel->setVisible(false);
    m_statusLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse
                                           | Qt::TextInteractionFlag::TextSelectableByKeyboard);

    // clang-format off
    Column {
        Row {
            m_button, m_progressIndicator, st
        },
        m_statusLabel
    }.attachTo(this);
    // clang-format on

    auto update = [this] {
        updateClient(settings().nodeJsPath.expandedVolatileValue(),
                     settings().distPath.expandedVolatileValue());
    };

    connect(m_button, &QPushButton::clicked, this, [this, update]() {
        if (m_status == Status::SignedIn)
            signOut();
        else if (m_status == Status::SignedOut)
            signIn();
        else
            update();
    });

    connect(&settings(), &CopilotSettings::applied, this, update);
    connect(&settings().nodeJsPath, &FilePathAspect::volatileValueChanged, this, update);
    connect(&settings().distPath, &FilePathAspect::volatileValueChanged, this, update);

    update();
}

AuthWidget::~AuthWidget()
{
    if (m_client)
        LanguageClientManager::shutdownClient(m_client);
}

void AuthWidget::setState(const QString &buttonText, const QString &errorText, bool working)
{
    m_button->setText(buttonText);
    m_button->setVisible(true);
    m_progressIndicator->setVisible(working);
    m_statusLabel->setText(errorText);
    m_statusLabel->setVisible(!m_statusLabel->text().isEmpty());
    m_button->setEnabled(!working);
}

void AuthWidget::checkStatus()
{
    if (!isEnabled())
        return;

    QTC_ASSERT(m_client && m_client->reachable(), return);

    setState("Checking status ...", {}, true);

    m_client->requestCheckStatus(
        false, guardedCallback(this, [this](const CheckStatusRequest::Response &response) {
            if (response.error()) {
                setState("Failed to authenticate", response.error()->message(), false);
                return;
            }
            const CheckStatusResponse result = *response.result();

            if (result.user().isEmpty()) {
                setState("Sign in", {}, false);
                m_status = Status::SignedOut;
                return;
            }

            setState("Sign out " + result.user(), {}, false);
            m_status = Status::SignedIn;
        }));
}

void AuthWidget::updateClient(const FilePath &nodeJs, const FilePath &agent)
{
    LanguageClientManager::shutdownClient(m_client);
    m_client = nullptr;
    setState(Tr::tr("Sign In"), {}, false);
    m_button->setEnabled(false);
    if (!nodeJs.isExecutableFile() || !agent.exists())
        return;

    setState(Tr::tr("Sign In"), {}, true);

    m_client = new CopilotClient(nodeJs, agent);
    connect(m_client, &Client::initialized, this, &AuthWidget::checkStatus);
    connect(m_client, &QObject::destroyed, this, [destroyedClient = m_client, this]() {
        if (destroyedClient != m_client)
            return;
        m_client = nullptr;
        m_progressIndicator->hide();
    });
}

void AuthWidget::signIn()
{
    qCritical() << "Not implemented";
    QTC_ASSERT(m_client && m_client->reachable(), return);

    setState("Signing in ...", {}, true);

    m_client->requestSignInInitiate(
        guardedCallback(this, [this](const SignInInitiateRequest::Response &response) {
            if (response.error()) {
                const QString error
                    = Tr::tr("The sign-in request failed: %1").arg(response.error()->message());
                setState("Sign in", error, false);
                return;
            }

            Utils::setClipboardAndSelection(response.result()->userCode());

            QDesktopServices::openUrl(QUrl(response.result()->verificationUri()));

            m_statusLabel->setText(Tr::tr("A browser window will open. Enter the code %1 when "
                                          "asked.\nThe code has been copied to your clipboard.")
                                       .arg(response.result()->userCode()));
            m_statusLabel->setVisible(true);

            m_client->requestSignInConfirm(
                response.result()->userCode(),
                guardedCallback(this, [this](const SignInConfirmRequest::Response &response) {
                    if (response.error()) {
                        QMessageBox::critical(
                            this,
                            Tr::tr("Login Failed"),
                            Tr::tr("The login request failed: %1").arg(response.error()->message()));
                        setState("Sign in", response.error()->message(), false);
                        return;
                    }

                    setState("Sign Out " + response.result()->user(), {}, false);
                }));
        }));
}

void AuthWidget::signOut()
{
    QTC_ASSERT(m_client && m_client->reachable(), return);

    setState("Signing out ...", {}, true);

    m_client->requestSignOut(guardedCallback(this, [this](const SignOutRequest::Response &response) {
        QTC_ASSERT(!response.error(), return);
        QTC_ASSERT(response.result()->status() == "NotSignedIn", return);

        checkStatus();
    }));
}

} // namespace Copilot
