<?php

namespace App\Commands;

use App\Actions\Init\BuildEnvValues;
use App\Actions\Init\CloneStarter;
use App\Actions\Init\EnsureTargetDirectory;
use App\Actions\Init\InitializeGit;
use App\Actions\Init\InstallProject;
use App\Actions\Init\WriteEnv;
use App\Services\Command;
use App\Services\ProjectDoctor;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use function Laravel\Prompts\error;
use function Laravel\Prompts\info;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;

class Init extends Command
{
    protected $signature = 'init
        {directory? : Target directory for the new project}
        {--project-type= : `new` or `existing`}
        {--folder-mode= : `create` or `current`}
        {--repository= : Git remote to add as origin}
        {--slug= : Project slug used for local defaults}
        {--url= : Local project url}
        {--db-name= : Database name}
        {--db-host=host.db.docker.internal:7689 : Database host with port}
        {--starter-repo=git@github.com:probots-io/probots-wp-starter-2026.git : Starter repository to clone}';

    protected $description = "Initialize a new project from the starter";

    public function handle(
        ProjectDoctor $doctor,
        EnsureTargetDirectory $ensureTargetDirectory,
        CloneStarter $cloneStarter,
        InitializeGit $initializeGit,
        BuildEnvValues $buildEnvValues,
        WriteEnv $writeEnv,
        InstallProject $installProject,
    ): int {
        try {
            [$projectType, $folderMode] = $this->resolveModes();

            if ($projectType === "existing") {
                error("Initializing existing projects is not implemented yet.");

                return SymfonyCommand::FAILURE;
            }

            $projectDir = $this->resolveProjectDir($folderMode);
            $slugDefault = basename($projectDir);

            $hostResults = $doctor->checkHost();
            $this->renderDoctorResults($hostResults);

            if ($doctor->hasErrors($hostResults)) {
                return SymfonyCommand::FAILURE;
            }

            $ensureTargetDirectory->handle($projectDir);

            info("Cloning starter...");
            $cloneStarter->handle(
                (string) $this->option("starter-repo"),
                $projectDir,
            );

            $slug = $this->valueOrPrompt("slug", "Project slug", $slugDefault);
            $repositoryUrl = $this->valueOrPrompt(
                "repository",
                "Repository ssh url",
                "",
            );
            $url = $this->valueOrPrompt("url", "Local url", $slug . ".prbts");
            $dbName = $this->valueOrPrompt("db-name", "Database name", $slug);
            $dbHost = $this->valueOrPrompt(
                "db-host",
                "Database host",
                "host.db.docker.internal:7689",
            );

            info("Initializing git...");
            $initializeGit->handle($projectDir, $repositoryUrl);

            info("Writing .env...");
            $writeEnv->handle(
                $projectDir,
                $buildEnvValues->handle($slug, $url, $dbName, $dbHost),
            );

            info("Running project checks...");
            $projectResults = $doctor->checkProject($projectDir);
            $this->renderDoctorResults($projectResults);

            if ($doctor->hasErrors($projectResults)) {
                return SymfonyCommand::FAILURE;
            }

            info("Starting project services and installing dependencies...");
            $installProject->handle($projectDir);

            info(sprintf("Project ready at http://%s", $url));
            info(sprintf("Project directory: %s", $projectDir));

            return SymfonyCommand::SUCCESS;
        } catch (\RuntimeException $exception) {
            error($exception->getMessage());

            return SymfonyCommand::FAILURE;
        } catch (\Throwable $exception) {
            error($exception->getMessage());

            return SymfonyCommand::FAILURE;
        }
    }

    private function resolveModes(): array
    {
        $projectType = $this->option("project-type");
        $folderMode = $this->option("folder-mode");

        if ($projectType === null xor $folderMode === null) {
            throw new \RuntimeException(
                "When using non-interactive mode, both `--project-type` and `--folder-mode` are required.",
            );
        }

        if ($projectType !== null || $folderMode !== null) {
            $resolvedProjectType = $this->validateProjectType(
                (string) $projectType,
            );
            $resolvedFolderMode = $this->validateFolderMode(
                (string) $folderMode,
            );

            return [$resolvedProjectType, $resolvedFolderMode];
        }

        $resolvedProjectType = select(
            label: "What do you want to initialize?",
            options: [
                "new" => "New project",
                "existing" => "Existing project",
            ],
            default: "new",
        );

        if ($resolvedProjectType === "existing") {
            return [$resolvedProjectType, "current"];
        }

        $resolvedFolderMode = select(
            label: "Where should the project be created?",
            options: [
                "create" => "Create new folder",
                "current" => "Use current folder",
            ],
            default: "create",
        );

        return [$resolvedProjectType, $resolvedFolderMode];
    }

    private function validateProjectType(string $projectType): string
    {
        if (!in_array($projectType, ["new", "existing"], true)) {
            throw new \RuntimeException(
                "`--project-type` must be either `new` or `existing`.",
            );
        }

        return $projectType;
    }

    private function validateFolderMode(string $folderMode): string
    {
        if (!in_array($folderMode, ["create", "current"], true)) {
            throw new \RuntimeException(
                "`--folder-mode` must be either `create` or `current`.",
            );
        }

        return $folderMode;
    }

    private function resolveProjectDir(string $folderMode): string
    {
        if ($folderMode === "current") {
            return getcwd();
        }

        $directory = $this->argument("directory");

        if (!is_string($directory) || $directory === "") {
            if ($this->isNonInteractiveMode()) {
                throw new \RuntimeException(
                    "Missing required project directory for `--folder-mode=create`.",
                );
            }

            $directory = text(
                label: "Project directory",
                placeholder: "my-project",
                required: true,
            );
        }

        if (str_starts_with($directory, "/")) {
            return $directory;
        }

        return getcwd() . "/" . $directory;
    }

    private function valueOrPrompt(
        string $option,
        string $label,
        string $default,
    ): string {
        $value = $this->option($option);

        if (is_string($value) && $value !== "") {
            return $value;
        }

        if ($this->isNonInteractiveMode()) {
            throw new \RuntimeException(
                sprintf(
                    "Missing required option `--%s` for non-interactive init.",
                    $option,
                ),
            );
        }

        return text(label: $label, default: $default, required: true);
    }

    private function isNonInteractiveMode(): bool
    {
        return $this->option("project-type") !== null ||
            $this->option("folder-mode") !== null;
    }

    private function renderDoctorResults(array $results): void
    {
        foreach ($results as $result) {
            $prefix = match ($result["status"]) {
                "ok" => "OK",
                "warning" => "WARN",
                default => "ERR",
            };

            $this->line(
                sprintf(
                    "[%s] %s: %s",
                    $prefix,
                    $result["label"],
                    $result["message"],
                ),
            );
        }
    }
}
