#include "nix/store/uds-remote-store.hh"
#include "nix/util/environment-variables.hh"
#include "nix/util/unix-domain-socket.hh"
#include "nix/store/worker-protocol.hh"
#include "nix/store/store-registration.hh"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef _WIN32
#  include <winsock2.h>
#  include <afunix.h>
#else
#  include <sys/socket.h>
#  include <sys/un.h>
#endif

namespace nix {

std::filesystem::path getDaemonSocketPath(const Store::Config & config)
{
    return getEnvOsNonEmpty(OS_STR("NIX_DAEMON_SOCKET_PATH"))
        .transform([](auto && s) { return std::filesystem::path{s}; })
        .value_or(config.getStateDir() / "daemon-socket" / "socket");
}

void UDSRemoteStoreConfig::anchor() {}

void UDSRemoteStore::anchor() {}

UDSRemoteStoreConfig::UDSRemoteStoreConfig(const std::filesystem::path & path, const StoreReference::Params & params)
    : Store::Config{params, FilePathType::Native}
    , LocalFSStore::Config{params}
    , RemoteStore::Config{params, FilePathType::Native}
    , path{path.empty() ? getDaemonSocketPath(*this) : path}
{
}

std::string UDSRemoteStoreConfig::doc()
{
    return
#include "uds-remote-store.md"
        ;
}

// A bit gross that we now pass empty string but this is knowing that
// empty string will later default to the per-store socket path based
// on stateDir. Why don't we just wire it all through? I believe there
// are cases where it will live reload so we want to continue to
// account for that.
UDSRemoteStoreConfig::UDSRemoteStoreConfig(const Params & params)
    : UDSRemoteStoreConfig("", params)
{
}

UDSRemoteStore::UDSRemoteStore(ref<const Config> config)
    : Store{*config}
    , LocalFSStore{*config}
    , RemoteStore{*config}
    , config{config}
{
}

StoreReference UDSRemoteStoreConfig::getReference() const
{
    /* We specifically return "daemon" here instead of "unix://" or "unix://${path}"
     * to be more compatible with older versions of nix. Some tooling out there
     * tries hard to parse store references and it might not be able to handle "unix://". */
    if (path == getDaemonSocketPath(*this))
        return {
            .variant = StoreReference::Daemon{},
            .params = getQueryParams(),
        };
    return {
        .variant =
            StoreReference::Specified{
                .scheme = *uriSchemes().begin(),
                .authority = encodeUrlPath(pathToUrlPath(path)),
            },
        .params = getQueryParams(),
    };
}

void UDSRemoteStore::Connection::closeWrite()
{
    shutdown(toSocket(fd.get()), SHUT_WR);
}

ref<RemoteStore::Connection> UDSRemoteStore::openConnection()
{
    auto conn = make_ref<Connection>();

    /* Connect to a daemon that does the privileged work for us. */
    conn->fd = nix::connect(config->path);

    conn->from.fd = conn->fd.get();
    conn->to.fd = conn->fd.get();

    conn->startTime = std::chrono::steady_clock::now();

    return conn;
}

void UDSRemoteStore::addIndirectRoot(const std::filesystem::path & path)
{
    auto conn(getConnection());
    conn->to << WorkerProto::Op::AddIndirectRoot << path.string();
    conn.processStderr();
    readInt(conn->from);
}

ref<Store> UDSRemoteStore::Config::openStore() const
{
    return make_ref<UDSRemoteStore>(ref{shared_from_this()});
}

static RegisterStoreImplementation<UDSRemoteStore::Config> regUDSRemoteStore;

} // namespace nix
