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

#pragma once

#include "cmdbridgeglobal.h"

#include <utils/devicefileaccess.h>
#include <utils/environment.h>
#include <utils/filepath.h>
#include <utils/processinterface.h>
#include <utils/qtcprocess.h>

#include <functional>
#include <memory>

class tst_CmdBridge;

namespace CmdBridge {

class Client;

// RAII handle returned by forwardLocalSocketServer().  Keep it alive for as
// long as the forwarding should remain active; destroying it tears down the
// Go-side socket server and all associated connections.
class QTCREATOR_CMDBRIDGE_EXPORT LocalSocketForward
{
public:
    virtual ~LocalSocketForward() = default;

    // Path of the Unix socket server that was created on the remote device by
    // the Go bridge.  Remote processes should connect to this path.
    virtual Utils::FilePath path() const = 0;
};

class QTCREATOR_CMDBRIDGE_EXPORT FileAccess : public Utils::DeviceFileAccess
{
    friend class ::tst_CmdBridge;
    friend class GoFilePathWatcher;

public:
    FileAccess(const std::function<void()> &errorExitHandler = {});
    ~FileAccess() override;

    struct DeployError
    {
        QString message;
        enum Code { Disabled, EchoTestFailed, Other } code;
    };
    using DeployResult = Utils::expected<void, DeployError>;
    DeployResult deployAndInit(
        const Utils::FilePath &libExecPath,
        const Utils::FilePath &remoteRootPath,
        const Utils::Environment &environment);

    // Transport for putting the bridge onto a device that cannot receive it over its shell.
    using Uploader
        = std::function<Utils::Result<>(const Utils::FilePath &from, const Utils::FilePath &to)>;

    // Windows has none of the shell tools deployAndInit() uses, so the binary goes over the
    // given transport and stays on the device.
    DeployResult deployAndInitWindows(
        const Utils::FilePath &libExecPath,
        const Utils::FilePath &remoteRootPath,
        const Utils::Environment &environment,
        const Uploader &uploader);

    Utils::Result<> init(
        const Utils::FilePath &pathToBridge,
        const Utils::Environment &environment,
        bool deleteOnExit);

    // The binary the bridge runs from, for a caller that wants to start the same one again.
    Utils::FilePath bridgePath() const { return m_bridgePath; }

    // A system that only executes binaries carrying its own signature needs the
    // bridge prepared before it is uploaded.
    using ExecutablePreparer = std::function<Utils::Result<QByteArray>(const QByteArray &)>;
    void setExecutablePreparer(const ExecutablePreparer &preparer);

    void setPidMarker(const QString &marker);

    Utils::Result<> signalProcess(int pid, Utils::ControlSignal signal) const;

    // Connects a local QLocalSocket to the Unix socket server at
    // localSocketServerPath, then asks the Go bridge to create a new socket
    // server on the remote device.  Remote processes connecting to that server
    // have their data tunneled through the cmdbridge to the local App.
    // Returns a handle; path() gives the remote server path.  Drop the handle
    // to stop forwarding.
    Utils::Result<std::unique_ptr<LocalSocketForward>> forwardLocalSocketServer(
        const QString &localSocketServerPath);

    Utils::Result<Utils::Environment> deviceEnvironment() const override;

protected:
    Utils::Result<> reinit();

    Utils::Result<> iterateDirectory(const Utils::FilePath &filePath,
                          const Utils::FilePath::IterateDirCallback &callBack,
                          const Utils::FileFilter &filter) const override;

    Utils::Result<bool> isExecutableFile(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isReadableFile(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isWritableFile(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isReadableDirectory(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isWritableDirectory(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isFile(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isDirectory(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isSymLink(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> exists(const Utils::FilePath &filePath) const override;

    Utils::Result<bool> hasHardLinks(const Utils::FilePath &filePath) const override;
    Utils::Result<Utils::FilePathInfo> filePathInfo(const Utils::FilePath &filePath) const override;
    Utils::Result<Utils::FilePath> symLinkTarget(const Utils::FilePath &filePath) const override;
    Utils::Result<QDateTime> lastModified(const Utils::FilePath &filePath) const override;
    Utils::Result<QFile::Permissions> permissions(const Utils::FilePath &filePath) const override;
    Utils::Result<> setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const override;
    Utils::Result<qint64> fileSize(const Utils::FilePath &filePath) const override;
    Utils::Result<QString> owner(const Utils::FilePath &filePath) const override;
    Utils::Result<uint> ownerId(const Utils::FilePath &filePath) const override;
    Utils::Result<QString> group(const Utils::FilePath &filePath) const override;
    Utils::Result<uint> groupId(const Utils::FilePath &filePath) const override;
    Utils::Result<qint64> bytesAvailable(const Utils::FilePath &filePath) const override;
    Utils::Result<QByteArray> fileId(const Utils::FilePath &filePath) const override;
    Utils::Result<bool> isSameFile(const Utils::FilePath &lhs, const Utils::FilePath &rhs) const override;

    Utils::Result<QByteArray> fileContents(const Utils::FilePath &filePath,
                                                 qint64 limit,
                                                 qint64 offset) const override;
    Utils::Result<qint64> writeFileContents(const Utils::FilePath &filePath,
                                                  const QByteArray &data) const override;

    Utils::Result<> removeFile(const Utils::FilePath &filePath) const override;
    Utils::Result<> removeRecursively(const Utils::FilePath &filePath) const override;

    Utils::Result<> ensureExistingFile(const Utils::FilePath &filePath) const override;
    Utils::Result<> createDirectory(const Utils::FilePath &filePath) const override;

    Utils::Result<> copyFile(const Utils::FilePath &filePath,
                           const Utils::FilePath &target) const override;
    Utils::Result<> createSymLink(
        const Utils::FilePath &filePath, const Utils::FilePath &symLink) const override;

    Utils::Result<> renameFile(
        const Utils::FilePath &filePath, const Utils::FilePath &target) const override;

    Utils::Result<Utils::FilePath> createTempFile(const Utils::FilePath &filePath) override;
    Utils::Result<Utils::FilePath> createTempDir(const Utils::FilePath &filePath) override;
    Utils::Result<Utils::FilePath> createTemp(const Utils::FilePath &filePath, bool dir);

    std::vector<Utils::Result<std::unique_ptr<Utils::FilePathWatcher>>> watch(
        const Utils::FilePaths &filePaths) const override;

private:
    std::unique_ptr<CmdBridge::Client> m_client;
    Utils::FilePath m_bridgePath;
    QString m_pidMarker;
    bool m_started = false;
    ExecutablePreparer m_executablePreparer;
    Utils::Environment m_environment;
    const std::function<void()> m_errorExitHandler;
};

} // namespace CmdBridge
