package com.petshop.backend;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;

final class DockerComposeSupport {

    private static final int DATABASE_WAIT_ATTEMPTS = 30;

    private final Path projectDir;
    private final Function<List<String>, CommandResult> commandRunner;

    DockerComposeSupport() {
        this(Paths.get("").toAbsolutePath(), null);
    }

    DockerComposeSupport(Path projectDir, Function<List<String>, CommandResult> commandRunner) {
        this.projectDir = projectDir;
        this.commandRunner = commandRunner != null ? commandRunner : command -> runCommand(command, true);
    }

    void ensureDockerAvailable() {
        CommandResult versionResult = runCommand(List.of(resolveDockerExecutable(), "version"), false);
        if (versionResult.exitCode != 0) {
            throw new IllegalStateException(describeDockerFailure(versionResult.output));
        }
    }

    void startDatabase() {
        runCommand(composeCommand("up", "-d", "db"));
        waitForDatabaseHealth();
    }

    void stopDatabase() {
        runCommand(composeCommand("stop", "db"));
    }

    void resetDatabase() {
        runCommand(composeCommand("down", "-v", "--remove-orphans"));
        removeContainerIfPresent("petshop-api");
        removeContainerIfPresent("petshop-db");
        String volumeName = getDatabaseVolumeName();
        if (volumeExists(volumeName)) {
            runCommand(List.of(resolveDockerExecutable(), "volume", "rm", "-f", volumeName));
        }
    }

    boolean isDatabaseRunning() {
        CommandResult result = runCommand(composeCommand("ps", "--status", "running", "--services", "db"), false);
        if (result.exitCode != 0) {
            return false;
        }
        return result.output.lines()
                .map(String::trim)
                .anyMatch("db"::equals);
    }

    private boolean volumeExists(String volumeName) {
        CommandResult result = commandRunner.apply(List.of(resolveDockerExecutable(), "volume", "ls", "--format", "{{.Name}}"));
        if (result.exitCode != 0) {
            return false;
        }
        return result.output.lines()
                .map(String::trim)
                .anyMatch(volumeName::equals);
    }

    private String getDatabaseVolumeName() {
        return projectDir.getFileName().toString() + "_db_data";
    }

    private void waitForDatabaseHealth() {
        for (int attempt = 0; attempt < DATABASE_WAIT_ATTEMPTS; attempt++) {
            if (isContainerHealthy("petshop-db")) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
                throw new IllegalStateException("Database startup interrupted while waiting for MySQL.", ex);
            }
        }
        throw new IllegalStateException("Database startup failed because the petshop-db container did not become healthy in time.");
    }

    private boolean isContainerHealthy(String containerName) {
        CommandResult result = commandRunner.apply(List.of(resolveDockerExecutable(), "inspect", "--format", "{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}", containerName));
        if (result.exitCode != 0 || result.output == null) {
            return false;
        }
        return "healthy".equalsIgnoreCase(result.output.trim());
    }

    private List<String> composeCommand(String... args) {
        List<String> command = new ArrayList<>();
        command.add(resolveDockerExecutable());
        command.add("compose");
        command.add("-f");
        command.add("docker-compose.dev.yml");
        for (String arg : args) {
            command.add(arg);
        }
        return command;
    }

    private String resolveDockerExecutable() {
        String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
        if (os.contains("win")) {
            Path dockerPath = Paths.get("C:", "Program Files", "Docker", "Docker", "resources", "bin", "docker.exe");
            if (Files.isRegularFile(dockerPath)) {
                return dockerPath.toString();
            }
            Path dockerPathAlt = Paths.get("C:", "Program Files", "Docker", "Docker", "resources", "docker.exe");
            if (Files.isRegularFile(dockerPathAlt)) {
                return dockerPathAlt.toString();
            }
            return "docker.exe";
        }
        return "docker";
    }

    private void runCommand(List<String> command) {
        CommandResult result = commandRunner.apply(command);
        if (result.exitCode != 0) {
            throw new IllegalStateException(describeCommandFailure(command, result.output));
        }
    }

    private void removeContainerIfPresent(String containerName) {
        CommandResult result = commandRunner.apply(List.of(resolveDockerExecutable(), "rm", "-f", containerName));
        if (result.exitCode != 0) {
            String output = result.output == null ? "" : result.output.toLowerCase(Locale.ROOT);
            if (!output.contains("no such container")) {
                throw new IllegalStateException(describeCommandFailure(List.of(resolveDockerExecutable(), "rm", "-f", containerName), result.output));
            }
        }
    }

    private CommandResult runCommand(List<String> command, boolean printOutput) {
        ProcessBuilder builder = new ProcessBuilder(command);
        builder.directory(projectDir.toFile());
        builder.redirectErrorStream(true);

        try {
            Process process = builder.start();
            StringBuilder output = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append(System.lineSeparator());
                    if (printOutput) {
                        System.out.println(line);
                    }
                }
            }
            int exitCode = process.waitFor();
            return new CommandResult(exitCode, output.toString().trim());
        } catch (IOException ex) {
            String executable = command.isEmpty() ? "docker" : command.getFirst();
            throw new IllegalStateException("Unable to run " + executable + ". Install Docker Desktop and make sure it is available on PATH.", ex);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw new IllegalStateException("Docker command interrupted", ex);
        }
    }

    private String describeDockerFailure(String output) {
        String lowerOutput = "";
        if (output != null) {
            lowerOutput = output.toLowerCase(Locale.ROOT);
        }
        if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine") || lowerOutput.contains("cannot connect") || lowerOutput.contains("failed to connect") || lowerOutput.contains("docker_api") || lowerOutput.contains("docker api") || lowerOutput.contains("docker_engine") || lowerOutput.contains("pipe/docker_engine") || lowerOutput.contains("npipe")) {
            return "Docker Desktop is not running. Start Docker Desktop and rerun the command.";
        }
        if (output == null || output.isBlank()) {
            return "Docker is unavailable. Start Docker Desktop and rerun the command.";
        }
        return output;
    }

    private String describeCommandFailure(List<String> command, String output) {
        String renderedCommand = String.join(" ", command);
        String lowerOutput = "";
        if (output != null) {
            lowerOutput = output.toLowerCase(Locale.ROOT);
        }
        if (renderedCommand.contains(" up ") || renderedCommand.endsWith(" up -d db")) {
            if (lowerOutput.contains("port is already allocated") || lowerOutput.contains("address already in use")) {
                return "Database startup failed because port 3306 is already in use. Stop the conflicting MySQL service or container, then run Reset Database.";
            }
            if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine")) {
                return "Database startup failed because Docker Desktop is not running.";
            }
            if (output == null || output.isBlank()) {
                return "Database startup failed while bringing up the Docker MySQL container.";
            }
            return output;
        }
        if (renderedCommand.contains(" volume rm ")) {
            if (output == null || output.isBlank()) {
                return "Database reset failed while removing the Docker volume.";
            }
            return output;
        }
        if (output == null || output.isBlank()) {
            return "Command failed: " + renderedCommand;
        }
        return output;
    }

    record CommandResult(int exitCode, String output) {
    }

}
