#include "git_client.h"
#include "update.h"
#include "worker.h"
#include "utils.h"

#include <QRegularExpression>
#include <QDebug>
#include <QDir>


GitClient::GitClient(QString const &customerNrStr,
                     QString const &customerRepository,
                     QString const &workingDirectory,
                     QString const &branchName,
                     QObject *parent)
  : QObject(parent)
  , m_worker(qobject_cast<Worker *>(parent))
  , m_repositoryPath(QString("https://git.mimbach49.de/GerhardHoffmann/%1.git").arg(customerNrStr))
  , m_customerNr(customerNrStr)
  , m_workingDirectory(workingDirectory)
  , m_branchName(branchName)
  , m_customerRepository(customerRepository) {
    if (!m_worker) {
        qCritical() << "ERROR CASTING PARENT TO WORKER FAILED";
    }
}

bool GitClient::gitCloneCustomerRepository() {
    QString gitCommand("git clone ");
    gitCommand += m_repositoryPath;
    Command c(gitCommand);

    qInfo() << "IN CURRENT WD" << m_workingDirectory
            << "CLONE" << m_repositoryPath << "...";

    if (c.execute(m_workingDirectory)) { // execute the command in wd
        QString const result = c.getCommandResult();
        if (!result.isEmpty()) {
            // Cloning into 'customer_281'...\n
            static QRegularExpression re("(^\\s*Cloning\\s+into\\s+[']\\s*)(.*)(\\s*['].*$)");
            QRegularExpressionMatch match = re.match(result);
            if (match.hasMatch()) {
                if (re.captureCount() == 3) { // start with full match (0), then the other 3 matches
                    if (match.captured(2).trimmed() == m_customerNr) {
                        qInfo() << "CLONING" << m_repositoryPath << "OK";
                        return true;
                    }
                }
            }
        }
        Utils::printCriticalErrorMsg(QString("ERROR CLONE RESULT HAS WRONG FORMAT. CLONE_RESULT=") + result);
    }
    return false;
}

bool GitClient::copyGitConfigFromMaster() { // only allowed when called in
                                            // master branch (???)
    if (QDir(m_customerRepository).exists()) {
        QString const cp = QString("cp .gitconfig .git/config");
        Command c("bash");
        if (c.execute(m_customerRepository, QStringList() << "-c" << cp)) {
            qInfo() << "cp .gitconfig .git/config OK";
            return true;
        }
        qCritical() << "ERROR cp .gitconfig .git/config";
    }
    return false;
}

QStringList GitClient::gitBranchNames() {
    // git config --global pager.branch false
    QStringList bNames;
    if (QDir(m_customerRepository).exists()) {
        QString gitCommand("git branch -a");
        Command c(gitCommand);
        if (c.execute(m_customerRepository)) {
            QString const result = c.getCommandResult();
            return result.split('\n');
        }
    }
    return bNames;
}

bool GitClient::gitCheckoutBranch() {
    // TODO: nachsehen, ob der Branch ueberhaupt existiert

    if (QDir(m_customerRepository).exists()) {
        int zoneNr = Utils::read1stLineOfFile("/mnt/system_data/zone_nr");
        m_branchName = (zoneNr != 0)
            ? QString("zg1/zone%1").arg(zoneNr) : "master";

        QString gitCommand("git checkout ");
        gitCommand += m_branchName;

        Command c(gitCommand);
        return c.execute(m_customerRepository); // execute command in customerRepo
    }
    Utils::printCriticalErrorMsg(QString("ERROR ") + m_customerRepository + " DOES NOT EXIST");
    return false;
}

bool GitClient::gitCloneAndCheckoutBranch() {
    qInfo() << "CLONE" << m_repositoryPath << "AND CHECKOUT" << m_branchName;
    if (gitCloneCustomerRepository()) {
        //if (copyGitConfigFromMaster()) {
            if (gitCheckoutBranch()) {
                return true;
            } else {
                // TODO
            }
        //}
    }
    Utils::printCriticalErrorMsg(QString("CLONE ") + m_repositoryPath + " AND CHECKOUT FAILED");
    return false;
}

QStringList GitClient::gitShowReason() {
    QStringList lst;
    if (QDir(m_customerRepository).exists()) {
        // %h: commit (short form)
        // %s: commit message
        // %cI: commit date, strict ISO 8601 format
        // Note: use master branch. By convention, there is a ChangeLog file
        // in the root of the repository, which has to be always the last file
        // to be checked in when the customer repository somehow changed.
        Command c("git show origin/master -s --format=\"c=%h m=%s d=%cI\"");
        if (c.execute(m_customerRepository)) {
            QString const s = c.getCommandResult().trimmed();
            int const c = s.indexOf("c=");
            int const m = s.indexOf("m=");
            int const d = s.indexOf("d=");

            QString msg = IsmasClient::getReasonForLastSendVersion();
            QString commit{""}, date{""};

            if (c != -1) {
                int start = c + 2;
                if (m >= start) {
                    int length = m - start;
                    commit = s.mid(start, length).trimmed();

                    start = m + 2;
                    if (d >= start) {
                        length = d - start;
                        msg += " (";
                        msg = s.mid(start, length).trimmed();
                        msg += ")";

                        start = d + 2;
                        date = s.mid(start);
                    }
                }

                if (!commit.isEmpty() && !msg.isEmpty() && !date.isEmpty()) {
                    lst << commit << msg << date;
                }
            }
        }
    } else {
        qCritical() << "CUSTOMER_REPOSITORY" << m_customerRepository
                    << "DOES NOT EXIST";
    }
    return lst;
}

/*
 Zu beachten: wird eine datei neu hinzugefuegt (git add/commit) dann aber gleich
 wieder geloscht, so wird sie im diff nicht angezeigt.
 */
std::optional<QStringList> GitClient::gitDiff(QString const &commits) {
    if (QDir(m_customerRepository).exists()) {
        // 409f198..6c22726
        QString gitCommand("git diff --compact-summary ");
        gitCommand += commits;

        Command c(gitCommand);
        if (c.execute(m_customerRepository)) { // execute command in local customerRepo
            QString s = c.getCommandResult().trimmed();
            Utils::printInfoMsg("GIT DIFF RESULT " + s);

            QStringList lines = Update::split(s, '\n');
            QStringList fileNames;
            // each line has the format "etc/psa_config/DC2C_print01.json | 1 +
            // or the format            "etc/psa_config/DC2C_print01.json (new) | 1 +
            // the filenames are relativ to the repository
            for (int i = 0; i < lines.size(); ++i) {
                QString const &line = lines.at(i);
                int newIndex = line.indexOf("(new");    // for new files
                int goneIndex = line.indexOf("(gone");  // for removed files
                int modeIndex = line.indexOf("(mode");
                int pipeIndex = line.indexOf('|');
                if (newIndex != -1) {
                    QString file = line.left(newIndex).trimmed();
                    qInfo() << "FILE (NEW)" << file;
                    fileNames << file;
                } else
                if (modeIndex != -1) {
                    QString const file = line.left(modeIndex).trimmed();
                    qInfo() << "FILE (MODE)" << file;
                    fileNames << file;
                } else
                if (goneIndex != -1) {
                    QString const file = line.left(goneIndex).trimmed();
                    qCritical() << "FILE (GONE)" << file;
                } else
                if (pipeIndex != -1) {
                    QString const file = line.left(pipeIndex).trimmed();
                    qInfo() << "FILE (PIPE)" << file;
                    fileNames << file;
                }
            }
            if (!fileNames.isEmpty()) {
                return fileNames;
            }
        }
    }
    return std::nullopt;
}

/*
 Hat sich nichts geaendert, so werden auch keine Commits <>..<> angezeigt
 */
std::optional<QString> GitClient::gitFetch() {
    if (QDir(m_customerRepository).exists()) {
        qInfo() << "BRANCH NAME" << m_branchName;

        Command c("git fetch");
        if (c.execute(m_customerRepository)) {
            QString const s = c.getCommandResult().trimmed();
            if (!s.isEmpty()) {
                QStringList lines = Update::split(s, '\n');
                if (!lines.empty()) {
                    int zoneNr = Utils::read1stLineOfFile("/mnt/system_data/zone_nr");
                    m_branchName = (zoneNr != 0) ? QString("zg1/zone%1").arg(zoneNr) : "master";
                    // lines can look like this:
                    // From https://git.mimbach49.de/GerhardHoffmann/customer_281
                    //   41ec581..5d25ac3  master     -> origin/master
                    //   ff10f57..43530a1  zg1/zone1  -> origin/zg1/zone1
                    //   6ed893f..5d9882c  zg1/zone2  -> origin/zg1/zone2
                    //   4384d17..77045d8  zg1/zone3  -> origin/zg1/zone3
                    //   89d2812..36a0d74  zg1/zone5  -> origin/zg1/zone5
                    bool found = false;
                    for (int i=0; i < lines.size(); ++i) {
                        if (lines.at(i).contains(m_branchName)) {
                            found = true;
                            // 409f198..6c22726  zg1/zone1  -> origin/zg1/zone1
                            static QRegularExpression re("(^\\s*)([0-9A-Fa-f]+..[0-9A-Fa-f]+)(.*$)");
                            QRegularExpressionMatch match = re.match(lines.at(i));
                            if (match.hasMatch()) {
                                if (re.captureCount() == 3) { // start with full match (0), then the other 3 matches
                                    return match.captured(2);
                                } else {
                                    emit m_worker->showErrorMessage("git fetch",
                                        QString("(wrong cap-count (%1)").arg(re.captureCount()));
                                }
                            } else {
                                emit m_worker->showErrorMessage("git fetch",
                                    "no regex-match for commits");
                                Utils::printCriticalErrorMsg("NO REGEX MATCH FOR COMMITS");
                            }
                        }
                    }
                    if (!found) {
                        emit m_worker->showErrorMessage("git fetch",
                            QString("unkown branch name ") + m_branchName);
                        Utils::printCriticalErrorMsg("UNKNOWN BRANCH NAME " + m_branchName);
                    }
                } else {
                    emit m_worker->showErrorMessage("git fetch",
                        QString("wrong format for result of 'git fetch' ") + s);
                    Utils::printCriticalErrorMsg(QString("WRONG FORMAT FOR RESULT OF 'GIT FETCH' ") + s);
                }
            } else {
                emit m_worker->showErrorMessage("git fetch", "empty result for 'git fetch'");
                Utils::printCriticalErrorMsg("EMPTY RESULT FOR 'GIT FETCH'");
            }
        }
    } else {
        emit m_worker->showErrorMessage("git fetch", QString("repository ") + m_customerRepository + " does not exist");
        Utils::printCriticalErrorMsg(QString("REPOSITORY ") + m_customerRepository + " DOES NOT EXIST");
    }
    return std::nullopt;
}

bool GitClient::gitFetchAndDiff() {
    if (gitFetch()) {
        QString gitCommand("git diff --compact-summary HEAD..FETCH_HEAD");
        Command c(gitCommand);
        return c.execute(m_workingDirectory);
    }
    return false;
}

bool GitClient::gitPull() {
    if (QDir(m_customerRepository).exists()) {
        Command c("git pull");
        if (c.execute(m_customerRepository)) {
            qInfo() << "PULLED INTO" << m_customerRepository;
            return true;
        }
        Utils::printCriticalErrorMsg(QString("PULL INTO " + m_customerRepository + " FAILED"));
    }
    return false;
}

std::optional<QStringList> GitClient::gitMerge() {
    Command c("git merge");
    if (c.execute(m_workingDirectory)) {
        QString s = c.getCommandResult();
        QStringList lst = Update::split(s, '\n');
        return lst;
    }
    return std::nullopt;
}

QString GitClient::gitLastCommit(QString fileName) {
    if (QDir(m_customerRepository).exists()) {
        QString const filePath
            = QDir::cleanPath(m_customerRepository + QDir::separator() + fileName);
        if (QFile(filePath).exists()) {
            QString const gitCommand = QString("git log %1 | head -n 1").arg(fileName);
            Command c("bash");
            if (c.execute(m_customerRepository, QStringList() << "-c" << gitCommand)) {
                QString const r = c.getCommandResult();
                int const idx = r.indexOf("commit ");
                if (idx != -1) {
                    return r.mid(idx + 8).trimmed();
                }
            }
        }
    }
    return "";
}

// fileName has to an absolute path
QString GitClient::gitBlob(QString fileName) {
    QFileInfo fi(fileName);
    if (fi.exists()) {
        QString const gitCommand = QString("git hash-object %1").arg(fileName);
        Command c(gitCommand);
        if (c.execute("/tmp")) {
            return c.getCommandResult().trimmed();
        }
    }
    return "N/A";
}

QString GitClient::gitCommitForBlob(QString blob) {
    if (QDir(m_customerRepository).exists()) {
        QString const gitCommand
            = QString("git whatchanged --all --find-object=%1 | head -n 1").arg(blob);
        Command c(gitCommand);
        if (c.execute(m_customerRepository)) {
            return c.getCommandResult();
        }
    }
    return "";
}

bool GitClient::gitIsFileTracked(QString fName) {
    if (QDir(m_customerRepository).exists()) {
        QString const gitCommand
            = QString("git ls-files --error-unmatch %1").arg(fName);
        Command c(gitCommand);
        return c.execute(m_customerRepository);
    }
    return false;
}


//get_commit_for_blob () {
//    # search for the blob in all commits for the file(name) $1
//    echo $(git log --all --pretty=format:%H -- $2   |
//           xargs -I{} bash -c "git ls-tree {} -- $2 |
//           grep -q $1 && echo -n {} && head -n 1")
//}
