#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
 * Wartungs-CLI.
 *
 *   php bin/ekdos migrate                Schema anlegen oder fortschreiben
 *   php bin/ekdos user:create            Benutzer anlegen (Passwort wird abgefragt)
 *   php bin/ekdos user:list              Benutzer auflisten
 *   php bin/ekdos user:password <name>   Passwort zuruecksetzen
 *   php bin/ekdos cache:flush            n8n-Cache leeren
 *   php bin/ekdos check                  Postgres, Redis und n8n pruefen
 *
 * Der erste Administrator wird mit user:create angelegt. Es gibt bewusst keinen
 * Bootstrap-Benutzer aus der Umgebung: ein Passwort in einer .env ist ein
 * Passwort, das niemand mehr aendert.
 */

use Ekdos\Auth\AuthService;
use Ekdos\Bootstrap\Container;
use Ekdos\N8n\Cache;
use Ekdos\N8n\Client;
use Ekdos\N8n\Endpoints;
use Ekdos\Support\Config;
use Ekdos\Users\Role;
use Ekdos\Users\UserRepository;

require __DIR__ . '/../vendor/autoload.php';

$projectRoot = dirname(__DIR__);
$command = $argv[1] ?? 'help';

function out(string $line = ''): void
{
    fwrite(STDOUT, $line . PHP_EOL);
}

function fail(string $line): never
{
    fwrite(STDERR, $line . PHP_EOL);
    exit(1);
}

/** Liest eine Eingabe ohne Bildschirmausgabe. */
function readSecret(string $prompt): string
{
    fwrite(STDERR, $prompt);

    if (DIRECTORY_SEPARATOR === '/' && function_exists('shell_exec')) {
        shell_exec('stty -echo 2>/dev/null');
    }

    $value = trim((string) fgets(STDIN));

    if (DIRECTORY_SEPARATOR === '/' && function_exists('shell_exec')) {
        shell_exec('stty echo 2>/dev/null');
    }

    fwrite(STDERR, PHP_EOL);

    return $value;
}

function ask(string $prompt, string $fallback = ''): string
{
    fwrite(STDERR, $prompt);
    $value = trim((string) fgets(STDIN));

    return $value !== '' ? $value : $fallback;
}

$container = Container::build($projectRoot);

switch ($command) {
    case 'migrate':
        /** @var PDO $db */
        $db = $container->get(PDO::class);
        $db->exec('create table if not exists schema_migrations (version text primary key, applied_at timestamptz not null default now())');

        $applied = $db->query('select version from schema_migrations')->fetchAll(PDO::FETCH_COLUMN);
        $files = glob($projectRoot . '/migrations/*.sql') ?: [];
        sort($files);
        $ran = 0;

        foreach ($files as $file) {
            $version = basename($file, '.sql');

            if (in_array($version, $applied, true)) {
                continue;
            }

            $sql = file_get_contents($file);

            if ($sql === false) {
                fail('Migration nicht lesbar: ' . $file);
            }

            $db->beginTransaction();

            try {
                $db->exec($sql);
                $statement = $db->prepare('insert into schema_migrations (version) values (:version)');
                $statement->execute(['version' => $version]);
                $db->commit();
            } catch (Throwable $exception) {
                $db->rollBack();
                fail('Migration ' . $version . ' fehlgeschlagen: ' . $exception->getMessage());
            }

            out('  angewendet: ' . $version);
            $ran++;
        }

        out($ran === 0 ? 'Das Schema ist aktuell.' : $ran . ' Migration(en) angewendet.');
        break;

    case 'user:create':
        /** @var UserRepository $users */
        $users = $container->get(UserRepository::class);

        $username = ask('Benutzername: ');
        $displayName = ask('Anzeigename: ', $username);
        $roleInput = ask('Rolle [admin|inhaber|buero] (admin): ', 'admin');
        $role = Role::tryFrom($roleInput) ?? fail('Unbekannte Rolle: ' . $roleInput);

        if ($username === '') {
            fail('Der Benutzername darf nicht leer sein.');
        }

        if ($users->usernameTaken($username)) {
            fail('Dieser Benutzername ist bereits vergeben.');
        }

        $password = readSecret('Passwort: ');

        if (($problem = AuthService::rejectWeakPassword($password)) !== null) {
            fail($problem);
        }

        if ($password !== readSecret('Passwort wiederholen: ')) {
            fail('Die Passwörter stimmen nicht überein.');
        }

        $user = $users->create($username, $displayName, $role, AuthService::hash($password));
        $users->audit(null, 'CLI', 'user.create', $user->id, $user->username, ['role' => $role->value]);
        out('Angelegt: ' . $user->username . ' (' . $user->role->label() . ')');
        break;

    case 'user:list':
        /** @var UserRepository $users */
        $users = $container->get(UserRepository::class);
        $rows = $users->all();

        if ($rows === []) {
            out('Es ist noch kein Benutzer angelegt. Anlegen mit: php bin/ekdos user:create');
            break;
        }

        out(sprintf('%-22s %-24s %-14s %-8s %s', 'BENUTZER', 'NAME', 'ROLLE', 'AKTIV', 'LETZTE ANMELDUNG'));

        foreach ($rows as $user) {
            out(sprintf(
                '%-22s %-24s %-14s %-8s %s',
                $user->username,
                $user->displayName,
                $user->role->value,
                $user->isActive ? 'ja' : 'nein',
                $user->lastLoginAt ?? '–',
            ));
        }
        break;

    case 'user:password':
        /** @var UserRepository $users */
        $users = $container->get(UserRepository::class);
        $username = $argv[2] ?? fail('Aufruf: php bin/ekdos user:password <benutzername>');
        $user = $users->findByUsernameWithHash($username) ?? fail('Unbekannter Benutzer: ' . $username);

        $password = readSecret('Neues Passwort für ' . $user->username . ': ');

        if (($problem = AuthService::rejectWeakPassword($password)) !== null) {
            fail($problem);
        }

        $users->update($user->id, ['password_hash' => AuthService::hash($password)]);
        $container->get(\Ekdos\Auth\SessionStore::class)->destroyAllFor($user->id);
        $users->audit(null, 'CLI', 'user.password', $user->id, $user->username);
        out('Passwort geändert. Offene Sitzungen dieses Benutzers wurden beendet.');
        break;

    case 'cache:flush':
        /** @var Cache $cache */
        $cache = $container->get(Cache::class);
        out($cache->flushAll() . ' zwischengespeicherte n8n-Antworten verworfen.');
        break;

    case 'check':
        /** @var Config $config */
        $config = $container->get(Config::class);
        $problems = 0;

        try {
            $container->get(PDO::class)->query('select 1');
            out('  Postgres  erreichbar');
        } catch (Throwable $exception) {
            out('  Postgres  FEHLER: ' . $exception->getMessage());
            $problems++;
        }

        try {
            $container->get(Redis::class)->ping();
            out('  Redis     erreichbar');
        } catch (Throwable $exception) {
            out('  Redis     FEHLER: ' . $exception->getMessage());
            $problems++;
        }

        /** @var Endpoints $endpoints */
        $endpoints = $container->get(Endpoints::class);
        /** @var Client $n8n */
        $n8n = $container->get(Client::class);
        $reply = $n8n->get($endpoints->openTickets());

        if ($reply->status === 0) {
            out('  n8n       FEHLER: nicht erreichbar unter ' . $endpoints->openTickets());
            $problems++;
        } else {
            out('  n8n       HTTP ' . $reply->status . ' von ' . $endpoints->openTickets());
        }

        foreach (['N8N_CUSTOMER_KEY', 'N8N_INVOICE_SYNC_SECRET'] as $secret) {
            if (!$config->has($secret)) {
                out('  Hinweis   ' . $secret . ' ist nicht gesetzt; betroffene Funktionen antworten mit 503.');
            }
        }

        exit($problems === 0 ? 0 : 1);

    default:
        out('EK-DOS-WEB Wartungs-CLI');
        out();
        out('  php bin/ekdos migrate                Schema anlegen oder fortschreiben');
        out('  php bin/ekdos user:create            Benutzer anlegen');
        out('  php bin/ekdos user:list              Benutzer auflisten');
        out('  php bin/ekdos user:password <name>   Passwort zurücksetzen');
        out('  php bin/ekdos cache:flush            n8n-Cache leeren');
        out('  php bin/ekdos check                  Postgres, Redis und n8n prüfen');
}
