package io.xpipe.app.process;

import io.xpipe.app.core.AppNames;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEventFactory;
import io.xpipe.app.util.FilePath;
import io.xpipe.app.util.OsType;

import java.io.IOException;

public class ShellTemp {

    public static FilePath createUserSpecificTempDataDirectory(ShellControl proc, String sub) throws Exception {
        FilePath base;
        // On Windows and macOS, we already have user specific temp directories
        // Even on macOS as root it is technically unique as only root will use /tmp
        if (proc.getOsType() != OsType.WINDOWS && proc.getOsType() != OsType.MACOS) {
            var temp = proc.getSystemTemporaryDirectory();
            base = temp.join(AppNames.ofCurrent().getKebapName());
            proc.view().mkdir(base);
            // We have to make sure that also other users can create files here
            // This command should work in all shells
            proc.command("chmod 777 " + proc.getShellDialect().fileArgument(base))
                    .executeAndCheck();
            var user = proc.view().user();
            base = base.join(user);
            // We have to make sure that also other users can create files here
            // This command should work in all shells
            proc.command("chmod 700 " + proc.getShellDialect().fileArgument(base))
                    .executeAndCheck();
        } else {
            var temp = proc.getSystemTemporaryDirectory();
            base = temp.join(AppNames.ofCurrent().getKebapName());
        }
        return sub != null ? base.join(sub) : base;
    }

    public static void checkTempDirectory(ShellControl sc) throws Exception {
        var d = sc.getShellDialect();
        var systemTemp = sc.getSystemTemporaryDirectory();
        var hasValidTemp = d.directoryExists(sc, systemTemp.toString()).executeAndCheck()
                && checkDirectoryPermissions(sc, systemTemp.toString());

        // We only really need temp for cmd
        // On various containers temp might be not available, but we can make it work
        if (!sc.isLocal() && sc.getShellDialect() == ShellDialects.CMD && !hasValidTemp) {
            throw ErrorEventFactory.expected(
                    new IOException("No permissions to access system temporary directory %s".formatted(systemTemp)));
        }

        if (hasValidTemp) {
            // When starting up multiple sessions to the same system, there might be race conditions here
            // This is quite inefficient but there is no way to synchronize access on a
            // specific system when multiple shell controls access it
            synchronized (ShellTemp.class) {
                var sessionFile = systemTemp.join("xpipe-session-"
                        + AppProperties.get().getSessionId().toString().substring(0, 8));
                var newSession = !sc.view().fileExists(sessionFile);
                if (newSession) {
                    clearTemp(sc);
                    sc.view().touch(sessionFile);
                }
            }
        }
    }

    public static void clearTemp(ShellControl sc) throws Exception {
        var systemTemp = sc.getSystemTemporaryDirectory();

        // The temp dir is a lot to clean on Windows potentially
        // Also, the wildcard remove is very slow in PowerShell
        var skipClear = OsType.ofLocal() == OsType.WINDOWS && sc.isLocal();
        if (!skipClear) {
            clearFiles(sc, systemTemp.join("xpipe-"));
        }
    }

    private static void clearFiles(ShellControl sc, FilePath prefix) throws Exception {
        var d = sc.getShellDialect();
        if (d == ShellDialects.CMD) {
            sc.command(CommandBuilder.of().add("DEL", "/Q", "/F").addQuoted(prefix.toString() + "*"))
                    .executeAndCheck();
        } else if (ShellDialects.isPowershell(d)) {
            sc.command(CommandBuilder.of()
                            .add("Get-ChildItem")
                            .addFile(prefix.getParent())
                            .add(
                                    "|",
                                    "Where-Object",
                                    "{-not $_.PSIsContainer}",
                                    "|",
                                    "Where-Object",
                                    "{$_.Name.StartsWith(\"" + prefix.getFileName() + "\")}",
                                    "|",
                                    "Remove-Item",
                                    "-Recurse",
                                    "-Force"))
                    .executeAndCheck();
        } else {
            sc.command(CommandBuilder.of()
                            .add("rm", "-f")
                            .add("\"" + prefix.toString() + "\"*")
                            .add("2>/dev/null"))
                    .executeAndCheck();
        }
    }

    private static boolean checkDirectoryPermissions(ShellControl proc, String dir) throws Exception {
        if (proc.getOsType() == OsType.WINDOWS) {
            return true;
        }

        var d = proc.getShellDialect();
        return proc.executeSimpleBooleanCommand("test -r %s && test -w %s && test -x %s"
                .formatted(d.fileArgument(dir), d.fileArgument(dir), d.fileArgument(dir)));
    }

    public static FilePath getSubDirectory(ShellControl proc, String... sub) throws Exception {
        var base = proc.getSystemTemporaryDirectory();
        var dir = base.join(sub);
        // We assume that this directory does not exist yet and therefore don't perform any checks
        proc.getShellDialect().prepareUserTempDirectory(proc, dir.toString()).execute();
        return dir;
    }
}
