#!/usr/bin/php
<?php
declare(strict_types=1);

require '/opt/ddns/lib/bootstrap.php';

use Ddns\Config;
use Ddns\JsonLine;

const WORKER_CONFIG = '/etc/opt/ddns/worker.php';

function usage(int $exit = 0): void
{
    $text = <<<'TXT'
Verwendung:
  ddns-admin worker ping [--json]
  ddns-admin doctor [--json]
  ddns-admin host add --fqdn NAME [--username USER] [--customer TEXT] (--ipv4|--ipv6|beide) [--json]
  ddns-admin host list [--customer TEXT] [--json]
  ddns-admin host show NAME [--json]
  ddns-admin host enable NAME [--json]
  ddns-admin host disable NAME [--json]
  ddns-admin token rotate NAME [--json]
  ddns-admin host delete NAME (--keep-dns|--delete-dns) [--yes] [--json]
  ddns-admin audit list [--limit N] [--json]
TXT;
    fwrite($exit === 0 ? STDOUT : STDERR, $text . "\n");
    exit($exit);
}

/** @return array{options:array<string,mixed>,positionals:list<string>} */
function parseArgs(array $args): array
{
    $options = [];
    $positionals = [];
    for ($i = 0, $count = count($args); $i < $count; ++$i) {
        $arg = $args[$i];
        if (!str_starts_with($arg, '--')) {
            $positionals[] = $arg;
            continue;
        }
        $name = substr($arg, 2);
        if ($name === '') {
            usage(2);
        }
        if (in_array($name, ['ipv4', 'ipv6', 'json', 'yes', 'keep-dns', 'delete-dns'], true)) {
            $options[$name] = true;
            continue;
        }
        if ($i + 1 >= $count || str_starts_with($args[$i + 1], '--')) {
            fwrite(STDERR, "Option --{$name} benötigt einen Wert.\n");
            usage(2);
        }
        $options[$name] = $args[++$i];
    }
    return ['options' => $options, 'positionals' => $positionals];
}

/** @param array<string,mixed> $request @return array<string,mixed> */
function adminRequest(array $request): array
{
    $config = Config::load(WORKER_CONFIG);
    return JsonLine::request(
        Config::string($config, 'admin_socket'),
        $request,
        Config::int($config, 'admin_timeout', 35, 2, 120)
    );
}

/** @param mixed $value */
function printJson($value): void
{
    echo json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), "\n";
}

/** @param list<array<string,mixed>> $rows */
function printHosts(array $rows): void
{
    printf("%-42s %-18s %-5s %-5s %-9s %-25s\n", 'HOSTNAME', 'KUNDE', 'A', 'AAAA', 'STATUS', 'LETZTES UPDATE');
    foreach ($rows as $row) {
        $updated = isset($row['last_verified_at']) && (int) $row['last_verified_at'] > 0
            ? date('Y-m-d H:i:s', (int) $row['last_verified_at'])
            : '-';
        printf(
            "%-42s %-18s %-5s %-5s %-9s %-25s\n",
            substr((string) $row['fqdn'], 0, 42),
            substr((string) ($row['customer'] ?? ''), 0, 18),
            !empty($row['allow_ipv4']) ? 'ja' : 'nein',
            !empty($row['allow_ipv6']) ? 'ja' : 'nein',
            !empty($row['active']) ? 'aktiv' : 'gesperrt',
            $updated
        );
    }
}

/** @param array<string,mixed> $host */
function printHost(array $host): void
{
    $labels = [
        'id' => 'ID', 'customer' => 'Kunde', 'fqdn' => 'Hostname', 'username' => 'Benutzername',
        'zone_suffix' => 'DynDNS-Zone', 'netcup_domain' => 'Netcup-Domain', 'record_name' => 'Recordname',
        'allow_ipv4' => 'IPv4 erlaubt', 'allow_ipv6' => 'IPv6 erlaubt', 'active' => 'Aktiv',
        'last_ipv4' => 'Letzte IPv4', 'last_ipv6' => 'Letzte IPv6', 'last_verified_at' => 'Zuletzt geprüft',
    ];
    foreach ($labels as $key => $label) {
        $value = $host[$key] ?? '-';
        if (is_bool($value)) {
            $value = $value ? 'ja' : 'nein';
        } elseif ($key === 'last_verified_at' && is_numeric($value) && (int) $value > 0) {
            $value = date('Y-m-d H:i:s', (int) $value);
        } elseif ($value === null || $value === '') {
            $value = '-';
        }
        printf("%-18s %s\n", $label . ':', (string) $value);
    }
}

try {
    $raw = $argv;
    array_shift($raw);
    if ($raw === [] || in_array($raw[0], ['-h', '--help', 'help'], true)) {
        usage(0);
    }

    $group = array_shift($raw);
    $command = null;
    if (in_array($group, ['host', 'token', 'worker', 'audit'], true)) {
        $command = array_shift($raw);
        if ($command === null) {
            usage(2);
        }
    }
    $parsed = parseArgs($raw);
    $o = $parsed['options'];
    $p = $parsed['positionals'];
    $json = !empty($o['json']);
    $action = '';
    $args = [];

    if ($group === 'worker' && $command === 'ping') {
        $action = 'ping';
    } elseif ($group === 'doctor') {
        $action = 'doctor';
    } elseif ($group === 'host' && $command === 'add') {
        if (!isset($o['fqdn']) || (!isset($o['ipv4']) && !isset($o['ipv6']))) {
            usage(2);
        }
        $action = 'host_add';
        $args = [
            'fqdn' => (string) $o['fqdn'],
            'username' => isset($o['username']) ? (string) $o['username'] : null,
            'customer' => isset($o['customer']) ? (string) $o['customer'] : '',
            'ipv4' => !empty($o['ipv4']),
            'ipv6' => !empty($o['ipv6']),
        ];
    } elseif ($group === 'host' && $command === 'list') {
        $action = 'host_list';
        $args = ['customer' => isset($o['customer']) ? (string) $o['customer'] : null];
    } elseif ($group === 'host' && in_array($command, ['show', 'enable', 'disable'], true)) {
        if (count($p) !== 1) {
            usage(2);
        }
        $action = 'host_' . $command;
        $args = ['fqdn' => $p[0]];
    } elseif ($group === 'token' && $command === 'rotate') {
        if (count($p) !== 1) {
            usage(2);
        }
        $action = 'token_rotate';
        $args = ['fqdn' => $p[0]];
    } elseif ($group === 'host' && $command === 'delete') {
        if (count($p) !== 1 || (!isset($o['keep-dns']) && !isset($o['delete-dns'])) || (isset($o['keep-dns']) && isset($o['delete-dns']))) {
            usage(2);
        }
        if (!empty($o['delete-dns']) && empty($o['yes'])) {
            fwrite(STDOUT, 'A-/AAAA-Records werden bei Netcup gelöscht. Fortfahren? [y/N] ');
            $answer = strtolower(trim((string) fgets(STDIN)));
            if (!in_array($answer, ['y', 'yes', 'j', 'ja'], true)) {
                fwrite(STDOUT, "Abgebrochen.\n");
                exit(1);
            }
        }
        $action = 'host_delete';
        $args = ['fqdn' => $p[0], 'keep_dns' => !empty($o['keep-dns']), 'delete_dns' => !empty($o['delete-dns'])];
    } elseif ($group === 'audit' && $command === 'list') {
        $action = 'audit_list';
        $args = ['limit' => isset($o['limit']) ? (int) $o['limit'] : 50];
    } else {
        usage(2);
    }

    $response = adminRequest(['op' => 'admin', 'action' => $action, 'args' => $args]);
    if (($response['ok'] ?? false) !== true) {
        throw new RuntimeException((string) ($response['error'] ?? 'Unbekannter Worker-Fehler.'));
    }
    $data = $response['data'] ?? [];
    if ($json) {
        printJson($data);
        exit(0);
    }

    switch ($action) {
        case 'ping':
            echo "Worker: OK\nVersion: ", (string) ($data['version'] ?? '-'), "\n";
            break;
        case 'doctor':
            printJson($data);
            break;
        case 'host_add':
            echo "Host erfolgreich angelegt.\n\n";
            echo "Hostname:    ", $data['fqdn'], "\n";
            echo "Benutzer:    ", $data['username'], "\n";
            echo "Token:       ", $data['token'], "\n";
            echo "Update-URL:  ", $data['update_url'], "\n";
            if (is_string($data['url_ipv4'] ?? null)) {
                echo "IPv4-URL:   ", $data['url_ipv4'], "\n";
            }
            if (is_string($data['url_ipv6'] ?? null)) {
                echo "IPv6-URL:   ", $data['url_ipv6'], "\n";
            }
            if (is_string($data['url_dual'] ?? null)) {
                echo "Dual-URL:   ", $data['url_dual'], "\n";
            }
            echo "\n", $data['notice'], "\n";
            break;
        case 'host_list':
            printHosts($data['hosts'] ?? []);
            break;
        case 'host_show':
            printHost($data['host'] ?? []);
            break;
        case 'host_enable':
        case 'host_disable':
            echo $data['fqdn'], ': ', !empty($data['active']) ? 'aktiv' : 'gesperrt', "\n";
            break;
        case 'token_rotate':
            echo "Hostname: ", $data['fqdn'], "\nNeues Token: ", $data['token'], "\n", $data['notice'], "\n";
            break;
        case 'host_delete':
            echo "Gelöscht: ", $data['fqdn'], "\nGelöschte DNS-Records: ", $data['deleted_dns_records'], "\n";
            break;
        case 'audit_list':
            foreach (($data['audit'] ?? []) as $row) {
                printf(
                    "%s %-8s %-18s %-10s %-42s %s\n",
                    date('Y-m-d H:i:s', (int) $row['created_at']),
                    (string) $row['actor'],
                    (string) $row['action'],
                    (string) $row['outcome'],
                    substr((string) ($row['fqdn'] ?? '-'), 0, 42),
                    (string) ($row['detail'] ?? '')
                );
            }
            break;
    }
} catch (Throwable $e) {
    fwrite(STDERR, 'Fehler: ' . $e->getMessage() . "\n");
    exit(1);
}
