<?php

namespace Beebmx\KirbySign\Actions;

use Kirby\CLI\CLI;
use Kirby\Cms\App;
use Kirby\Cms\Ingredients;
use Kirby\Filesystem\F;
use Kirby\Toolkit\Str;

class GenerateKeyCommand
{
    public function __invoke(CLI $cli): void
    {
        $key = $this->getKey($cli->kirby());

        if (! $cli->arg('env')) {
            $cli->out($key);
            $cli->success('Key generated successfully');

            return;
        }

        if (! $this->setKeyInEnvironmentFile($key, $cli)) {
            return;
        }

        $cli->success('Key was set in the .env file');
    }

    protected function getKey(App $kirby): string
    {
        return 'base64:'.base64_encode(
            $this->generateRandomKey(
                $kirby->option('beebmx.kirby-sign.cipher', 'aes-256-cbc')
            )
        );
    }

    protected function generateRandomKey(string $cipher): string
    {
        return random_bytes(
            $this->supportedChiphers()[strtolower($cipher)]['size'] ?? 32
        );
    }

    protected function supportedChiphers(): array
    {
        return [
            'aes-128-cbc' => ['size' => 16, 'aead' => false],
            'aes-256-cbc' => ['size' => 32, 'aead' => false],
            'aes-128-gcm' => ['size' => 16, 'aead' => true],
            'aes-256-gcm' => ['size' => 32, 'aead' => true],
        ];
    }

    protected function setKeyInEnvironmentFile(string $key, CLI $cli): bool
    {
        $kirby = $cli->kirby();
        $path = Str::replace(
            string: $cli->arg('path') ?? $cli->arg('PATH') ?? null,
            search: ['path=', 'PATH='],
            replace: ''
        );

        if (! $this->hasEnvironmentFile($kirby->roots(), $path)) {
            $cli->error('No .env file found.');

            return false;
        }

        if (! $this->writeEnvironmentFileWith(
            $key, $this->getEnvironmentFile($kirby->roots(), $path)
        )) {
            $cli->error('No KIRBY_KEY variable was found in the .env file.');

            return false;
        }

        return true;
    }

    protected function hasEnvironmentFile(Ingredients $roots, ?string $path = null): bool
    {
        return array_any($this->getEnvironmentPaths($roots, $path), fn ($location) => F::exists($location.'/.env'));
    }

    protected function getEnvironmentFile(Ingredients $roots, ?string $path = null): string
    {
        return array_find($this->getEnvironmentPaths($roots, $path), fn ($location) => F::exists($location.'/.env'));
    }

    protected function getEnvironmentPaths(Ingredients $roots, ?string $path = null): array
    {
        if (! empty($path) && ! F::exists($path)) {
            return [$path];
        }

        return $this->getCommonEnvironmentFileRoots($roots);
    }

    protected function getCommonEnvironmentFileRoots(Ingredients $roots): array
    {
        return [
            $roots->base(),
            $roots->index(),
            $roots->site(),
            $roots->storage(),
        ];
    }

    protected function writeEnvironmentFileWith(string $key, string $path): bool
    {
        $replaced = preg_replace(
            '/^KIRBY_KEY=.*$/m',
            'KIRBY_KEY='.$key,
            $input = F::read($path.'/.env')
        );

        if ($replaced === $input || $replaced === null) {
            return false;
        }

        file_put_contents($path.'/.env', $replaced);

        return true;
    }
}
