#include <QApplication>
#include <QCryptographicHash>
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QLocale>
#include <QMessageBox>
#include <QNetworkReply>
#include <QUrl>
#include <QtConcurrent>

#include "carddatabase.h"
#include "main.h"
#include "settingscache.h"
#include "spoilerbackgroundupdater.h"
#include "window_main.h"

#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"

SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
{
    isSpoilerDownloadEnabled = settingsCache->getDownloadSpoilersStatus();
    if (isSpoilerDownloadEnabled) {
        // Start the process of checking if we're in spoiler season
        // File exists means we're in spoiler season
        // We will load the database before attempting to download spoilers, incase they fail
        QtConcurrent::run(db, &CardDatabase::loadCardDatabases);
        startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false);
    }
}

void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool saveResults)
{
    auto spoilerURL = QUrl(url);
    downloadFromURL(spoilerURL, saveResults);
}

void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
{
    auto *nam = new QNetworkAccessManager(this);
    QNetworkReply *reply = nam->get(QNetworkRequest(url));

    if (saveResults) {
        // This will write out to the file (used for spoiler.xml)
        connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
    } else {
        // This will check the status (used to see if we're in spoiler season or not)
        connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled()));
    }
}

void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
{
    // Check for server reply
    auto *reply = dynamic_cast<QNetworkReply *>(sender());
    QNetworkReply::NetworkError errorCode = reply->error();

    if (errorCode == QNetworkReply::NoError) {
        spoilerData = reply->readAll();

        // Save the spoiler.xml file to the disk
        saveDownloadedFile(spoilerData);

        reply->deleteLater();
        emit spoilerCheckerDone();
    } else {
        qDebug() << "Error downloading spoilers file" << errorCode;
        emit spoilerCheckerDone();
    }
}

bool SpoilerBackgroundUpdater::deleteSpoilerFile()
{
    QString fileName = settingsCache->getSpoilerCardDatabasePath();
    QFileInfo fi(fileName);
    QDir fileDir(fi.path());
    QFile file(fileName);

    // Delete the spoiler.xml file
    if (file.exists() && file.remove()) {
        qDebug() << "Deleting spoiler.xml";
        return true;
    }

    qDebug() << "Error: Spoiler.xml not found or not deleted";
    return false;
}

void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
{
    auto *response = dynamic_cast<QNetworkReply *>(sender());
    QNetworkReply::NetworkError errorCode = response->error();

    if (errorCode == QNetworkReply::ContentNotFoundError) {
        // Spoiler season is offline at this point, so the spoiler.xml file can be safely deleted
        // The user should run Oracle to get the latest card information
        if (deleteSpoilerFile() && trayIcon) {
            trayIcon->showMessage(tr("Spoilers season has ended"), tr("Deleting spoiler.xml. Please run Oracle"));
        }

        qDebug() << "Spoiler Season Offline";
        emit spoilerCheckerDone();
    } else if (errorCode == QNetworkReply::NoError) {
        qDebug() << "Spoiler Service Online";
        startSpoilerDownloadProcess(SPOILERS_URL, true);
    } else if (errorCode == QNetworkReply::HostNotFoundError) {
        if (trayIcon) {
            trayIcon->showMessage(tr("Spoilers download failed"), tr("No internet connection"));
        }

        qDebug() << "Spoiler download failed due to no internet connection";
        emit spoilerCheckerDone();
    } else {
        if (trayIcon) {
            trayIcon->showMessage(tr("Spoilers download failed"), tr("Error") + " " + errorCode);
        }

        qDebug() << "Spoiler download failed with reason" << errorCode;
        emit spoilerCheckerDone();
    }
}

bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
{
    QString fileName = settingsCache->getSpoilerCardDatabasePath();
    QFileInfo fi(fileName);
    QDir fileDir(fi.path());

    if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
        return false;
    }

    // Check if the data matches. If it does, then spoilers are up to date.
    if (getHash(fileName) == getHash(data)) {
        if (trayIcon) {
            trayIcon->showMessage(tr("Spoilers already up to date"), tr("No new spoilers added"));
        }

        qDebug() << "Spoilers Up to Date";
        return false;
    }

    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly)) {
        qDebug() << "Spoiler Service Error: File open (w) failed for" << fileName;
        file.close();
        return false;
    }

    if (file.write(data) == -1) {
        qDebug() << "Spoiler Service Error: File write (w) failed for" << fileName;
        file.close();
        return false;
    }

    file.close();

    // Data written, so reload the card database
    qDebug() << "Spoiler Service Data Written";
    QtConcurrent::run(db, &CardDatabase::loadCardDatabases);

    // If the user has notifications enabled, let them know
    // when the database was last updated
    if (trayIcon) {
        QList<QByteArray> lines = data.split('\n');

        foreach (QByteArray line, lines) {
            if (line.contains("created:")) {
                QString timeStamp = QString(line).replace("created:", "").trimmed();
                timeStamp.chop(6); // Remove " (UTC)"

                auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss");
                utcTime.setTimeSpec(Qt::UTC);

                QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");

                trayIcon->showMessage(tr("Spoilers have been updated!"), tr("Last change:") + " " + localTime);
                emit spoilersUpdatedSuccessfully();
                return true;
            }
        }
    }

    return true;
}

QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName)
{
    QFile file(fileName);

    if (file.open(QFile::ReadOnly)) {
        // Only read the first 512 bytes (enough to get the "created" tag)
        const QByteArray bytes = file.read(512);

        QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
        hash.addData(bytes);

        qDebug() << "File Hash =" << hash.result();

        file.close();
        return hash.result();
    } else {
        qDebug() << "getHash ReadOnly failed!";
        file.close();
        return QByteArray();
    }
}

QByteArray SpoilerBackgroundUpdater::getHash(QByteArray data)
{
    // Only read the first 512 bytes (enough to get the "created" tag)
    const QByteArray bytes = data.left(512);

    QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
    hash.addData(bytes);

    qDebug() << "Data Hash =" << hash.result();

    return hash.result();
}
