#!/usr/bin/env php8.4
<?php
/**
 * bzcapi — CLI pour tester l'API Bzctoons v1
 *
 * Usage :
 *   chmod +x scripts/bzcapi
 *   export BZCTOONS_URL=https://pveman.bzctoons.net
 *   export BZCTOONS_TOKEN=your-api-token
 *
 *   ./scripts/bzcapi container:list
 *   ./scripts/bzcapi addon:attach 42 3 --size=20
 *   ./scripts/bzcapi addon:watch 42 abc123 --follow
 *   ./scripts/bzcapi addon:recover 42 abc123
 *   ./scripts/bzcapi webhook:subscribe https://my.app/hook --events=addon.operation.completed,addon.operation.failed
 *   ./scripts/bzcapi billing:snapshot
 */

// Autoload : fonctionne depuis le dossier du SDK (composer install), depuis vendor/bin
// (composer require bzctoons/php-sdk) ou depuis le dépôt de l'application.
foreach ([
    __DIR__ . '/vendor/autoload.php',
    __DIR__ . '/../../autoload.php',
    __DIR__ . '/../vendor/autoload.php',
    getcwd() . '/vendor/autoload.php',
] as $autoload) {
    if (is_file($autoload)) {
        require $autoload;
        break;
    }
}
if (!class_exists(\Symfony\Component\Console\Application::class)) {
    fwrite(STDERR, "Dépendances manquantes : exécutez `composer install` dans le dossier du SDK.\n");
    exit(1);
}
foreach ([__DIR__ . '/BzctoonsClient.php', __DIR__ . '/../sdk/BzctoonsClient.php'] as $client) {
    if (!class_exists('BzctoonsClient', false) && is_file($client)) {
        require_once $client;
    }
}

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

// ── Bootstrap ──────────────────────────────────────────────────────────────────

function makeClient(): BzctoonsClient
{
    $url   = getenv('BZCTOONS_URL')   ?: throw new \RuntimeException('BZCTOONS_URL non défini');
    $token = getenv('BZCTOONS_TOKEN') ?: throw new \RuntimeException('BZCTOONS_TOKEN non défini');

    return new BzctoonsClient($url, $token);
}

/** Message d'erreur lisible si l'API a refusé la requête (HTTP >= 400), sinon null. */
function apiFailure(array $result): ?string
{
    $status = (int) ($result['_status'] ?? 200);
    if ($status < 400) {
        return null;
    }
    $msgs = [];
    $walk = function ($node) use (&$walk, &$msgs): void {
        if (is_string($node)) {
            $msgs[] = $node;
        } elseif (is_array($node)) {
            if (isset($node['message']) && is_string($node['message'])) {
                $msgs[] = $node['message'];
            } else {
                foreach ($node as $child) {
                    $walk($child);
                }
            }
        }
    };
    $walk($result['errors'] ?? null);
    if (!$msgs && isset($result['message'])) {
        $msgs[] = (string) $result['message'];
    }

    return "Erreur API (HTTP {$status}) : " . ($msgs ? implode(' ', array_unique($msgs)) : 'réponse inattendue');
}

/** Format « 12,34 € » depuis des centimes. */
function eur(int|float|string|null $cents): string
{
    return number_format(((float) $cents) / 100, 2, ',', ' ') . ' €';
}

function jsonOut(OutputInterface $output, array $data): void
{
    $output->writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}

// ── Commands ──────────────────────────────────────────────────────────────────

// --- container:list -----------------------------------------------------------
$containerList = new class extends Command {
    protected function configure(): void
    {
        $this->setName('container:list')
            ->setDescription('Lister les conteneurs du compte')
            ->addOption('page', null, InputOption::VALUE_OPTIONAL, 'Numéro de page', 1)
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $result = makeClient()->listContainers(['page' => (int) $input->getOption('page')]);

        if ($error = apiFailure($result)) {
            $output->writeln("<error>{$error}</error>");
            return Command::FAILURE;
        }

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $items = $result['data'] ?? [];
        if (empty($items)) {
            $output->writeln('<comment>Aucun conteneur.</comment>');
            return Command::SUCCESS;
        }

        $table = new Table($output);
        $table->setHeaders(['ID', 'Nom', 'Statut', 'Plan', 'IP']);
        foreach ($items as $c) {
            $table->addRow([
                $c['id']             ?? '-',
                $c['hostname'] ?? $c['name'] ?? '-',
                $c['status']         ?? '-',
                $c['plan']['name']   ?? '-',
                $c['ipv4'] ?? $c['ip_address'] ?? '-',
            ]);
        }
        $table->render();

        return Command::SUCCESS;
    }
};

// --- container:show -----------------------------------------------------------
$containerShow = new class extends Command {
    protected function configure(): void
    {
        $this->setName('container:show')
            ->setDescription('Détails d\'un conteneur')
            ->addArgument('id', InputArgument::REQUIRED, 'ID du conteneur')
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $result = makeClient()->getContainer($input->getArgument('id'));

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        if ($error = apiFailure($result)) {
            $output->writeln("<error>{$error}</error>");
            return Command::FAILURE;
        }

        $c = $result['data'] ?? $result;
        $output->writeln("<info>Conteneur #{$c['id']}</info> — " . ($c['hostname'] ?? $c['name'] ?? '-'));
        $output->writeln("  Statut  : {$c['status']}");
        $output->writeln("  Plan    : " . ($c['plan']['name'] ?? '-'));
        $output->writeln("  IP      : " . ($c['ipv4'] ?? $c['ip_address'] ?? '-'));

        return Command::SUCCESS;
    }
};

// --- addon:list ---------------------------------------------------------------
$addonList = new class extends Command {
    protected function configure(): void
    {
        $this->setName('addon:list')
            ->setDescription('Lister les addons d\'un conteneur')
            ->addArgument('container', InputArgument::REQUIRED, 'ID du conteneur')
            ->addOption('status', null, InputOption::VALUE_OPTIONAL, 'Filtrer par statut (Active, Failed, …)')
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $filters = array_filter(['status' => $input->getOption('status')]);
        $result  = makeClient()->listAddons($input->getArgument('container'), $filters);

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $items = $result['data'] ?? [];
        if (empty($items)) {
            $output->writeln('<comment>Aucun addon.</comment>');
            return Command::SUCCESS;
        }

        $table = new Table($output);
        $table->setHeaders(['ID', 'Type', 'Statut', 'Taille', 'Activé le']);
        foreach ($items as $a) {
            $table->addRow([
                $a['id'],
                $a['type']['name']    ?? '-',
                $a['status']          ?? '-',
                $a['current_size']    ?? '-',
                $a['activated_at']    ?? '-',
            ]);
        }
        $table->render();

        return Command::SUCCESS;
    }
};

// --- addon:attach -------------------------------------------------------------
$addonAttach = new class extends Command {
    protected function configure(): void
    {
        $this->setName('addon:attach')
            ->setDescription('Attacher un addon à un conteneur')
            ->addArgument('container', InputArgument::REQUIRED, 'ID du conteneur')
            ->addArgument('type',      InputArgument::REQUIRED, 'ID du type d\'addon')
            ->addOption('size',   null, InputOption::VALUE_OPTIONAL, 'Taille initiale')
            ->addOption('watch',  null, InputOption::VALUE_NONE, 'Attendre la fin du provisionnement')
            ->addOption('json',   null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $client = makeClient();
        $result = $client->attachAddon(
            $input->getArgument('container'),
            (int) $input->getArgument('type'),
            $input->getOption('size') !== null ? (int) $input->getOption('size') : null
        );

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return ($result['_status'] ?? 200) < 400 ? Command::SUCCESS : Command::FAILURE;
        }

        if ($error = apiFailure($result)) {
            $output->writeln("<error>{$error}</error>");
            return Command::FAILURE;
        }

        $operationId = $result['meta']['operation']['id'] ?? null;
        $addonId     = $result['data']['id'] ?? null;

        $output->writeln("<info>Addon créé</info> : {$addonId}");
        $output->writeln("  Opération : {$operationId}");

        if ($input->getOption('watch') && $operationId) {
            $output->writeln('');
            $output->write('  En attente du provisionnement...');

            $final = $client->watchOperation(
                $operationId,
                function (string $status) use ($output) {
                    $output->write(" [{$status}]");
                },
                2000,
                120
            );

            $output->writeln('');
            $status = $final['data']['status'] ?? '?';
            if (BzctoonsClient::isSuccessStatus($status)) {
                $output->writeln('<info>  Provisionnement terminé avec succès.</info>');
            } else {
                $output->writeln("<error>  Opération terminée en état : {$status}</error>");
                return Command::FAILURE;
            }
        }

        return Command::SUCCESS;
    }
};

// --- addon:watch --------------------------------------------------------------
$addonWatch = new class extends Command {
    protected function configure(): void
    {
        $this->setName('addon:watch')
            ->setDescription('Surveiller une opération d\'addon jusqu\'à son état terminal')
            ->addArgument('operation', InputArgument::REQUIRED, 'ID ULID de l\'opération')
            ->addOption('timeout', null, InputOption::VALUE_OPTIONAL, 'Timeout en secondes', 120)
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute à la fin');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $client      = makeClient();
        $operationId = $input->getArgument('operation');
        $timeout     = (int) $input->getOption('timeout');

        $output->writeln("Surveillance de l'opération <info>{$operationId}</info>...");

        try {
            $final = $client->watchOperation(
                $operationId,
                function (string $status, array $op) use ($output) {
                    $attempt = $op['attempt'] ?? '?';
                    $output->writeln("  [{$status}] tentative #{$attempt}");
                },
                2000,
                $timeout
            );
        } catch (\RuntimeException $e) {
            $output->writeln("<error>{$e->getMessage()}</error>");
            return Command::FAILURE;
        }

        if ($input->getOption('json')) {
            jsonOut($output, $final);
        } else {
            $status = $final['data']['status'] ?? '?';
            $ok     = BzctoonsClient::isSuccessStatus($status);
            $icon   = $ok ? '✓' : '✗';
            $tag    = $ok ? 'info' : 'error';
            $output->writeln("<{$tag}>{$icon} {$status}</{$tag}>");
        }

        return BzctoonsClient::isSuccessStatus($final['data']['status'] ?? '') ? Command::SUCCESS : Command::FAILURE;
    }
};

// --- addon:recover ------------------------------------------------------------
$addonRecover = new class extends Command {
    protected function configure(): void
    {
        $this->setName('addon:recover')
            ->setDescription('Relancer le provisionnement d\'un addon en échec')
            ->addArgument('container', InputArgument::REQUIRED, 'ID du conteneur')
            ->addArgument('addon',     InputArgument::REQUIRED, 'ID de l\'addon')
            ->addOption('watch', null, InputOption::VALUE_NONE, 'Attendre la fin du reprovisionnement')
            ->addOption('json',  null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $client = makeClient();

        try {
            $result = $client->recoverAddon(
                $input->getArgument('container'),
                $input->getArgument('addon')
            );
        } catch (\RuntimeException $e) {
            $output->writeln("<error>{$e->getMessage()}</error>");
            return Command::FAILURE;
        }

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return ($result['_status'] ?? 200) < 400 ? Command::SUCCESS : Command::FAILURE;
        }

        if ($error = apiFailure($result)) {
            $output->writeln("<error>{$error}</error>");
            return Command::FAILURE;
        }

        $operationId = $result['meta']['operation']['id'] ?? null;
        $output->writeln("<info>Retry lancé</info>. Opération : {$operationId}");

        if ($input->getOption('watch') && $operationId) {
            $final  = $client->watchOperation(
                $operationId,
                fn(string $s) => $output->write(" [{$s}]"),
                2000,
                120
            );
            $output->writeln('');
            $status = $final['data']['status'] ?? '?';
            $ok = BzctoonsClient::isSuccessStatus($status);
            $output->writeln($ok
                ? '<info>Reprovisionnement réussi.</info>'
                : "<error>Échec : {$status}</error>"
            );

            return $ok ? Command::SUCCESS : Command::FAILURE;
        }

        return Command::SUCCESS;
    }
};

// --- addon:cancel -------------------------------------------------------------
$addonCancel = new class extends Command {
    protected function configure(): void
    {
        $this->setName('addon:cancel')
            ->setDescription('Annuler une opération d\'addon en attente (Pending)')
            ->addArgument('container', InputArgument::REQUIRED, 'ID du conteneur')
            ->addArgument('addon',     InputArgument::REQUIRED, 'ID de l\'addon')
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $result = makeClient()->cancelAddonOperation(
            $input->getArgument('container'),
            $input->getArgument('addon')
        );

        if ($input->getOption('json')) {
            jsonOut($output, $result);
        } else {
            $status = $result['_status'] ?? 0;
            if ($status === 200) {
                $output->writeln('<info>Opération annulée.</info>');
            } else {
                $output->writeln("<error>Erreur HTTP {$status}</error>");
                jsonOut($output, $result);
                return Command::FAILURE;
            }
        }

        return Command::SUCCESS;
    }
};

// --- addon:history ------------------------------------------------------------
$addonHistory = new class extends Command {
    protected function configure(): void
    {
        $this->setName('addon:history')
            ->setDescription('Historique des opérations d\'un addon')
            ->addArgument('container', InputArgument::REQUIRED, 'ID du conteneur')
            ->addArgument('addon',     InputArgument::REQUIRED, 'ID de l\'addon')
            ->addOption('status', null, InputOption::VALUE_OPTIONAL, 'Filtrer par statut')
            ->addOption('json',   null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $filters = array_filter(['status' => $input->getOption('status')]);
        $result  = makeClient()->addonOperationHistory(
            $input->getArgument('container'),
            $input->getArgument('addon'),
            $filters
        );

        if ($error = apiFailure($result)) {
            $output->writeln("<error>{$error}</error>");
            return Command::FAILURE;
        }

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $items = $result['data'] ?? [];
        if (empty($items)) {
            $output->writeln('<comment>Aucune opération.</comment>');
            return Command::SUCCESS;
        }

        $table = new Table($output);
        $table->setHeaders(['ID', 'Type', 'Statut', 'Tentative', 'Erreur', 'Démarrée le']);
        foreach ($items as $op) {
            $table->addRow([
                $op['id'],
                $op['type']        ?? '-',
                $op['status']      ?? '-',
                $op['attempt']     ?? '-',
                substr($op['error'] ?? '', 0, 50),
                $op['started_at']  ?? '-',
            ]);
        }
        $table->render();

        return Command::SUCCESS;
    }
};

// --- webhook:list -------------------------------------------------------------
$webhookList = new class extends Command {
    protected function configure(): void
    {
        $this->setName('webhook:list')
            ->setDescription('Lister les endpoints webhook revendeur')
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $result = makeClient()->listWebhookEndpoints();

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $items = $result['data'] ?? [];
        if (empty($items)) {
            $output->writeln('<comment>Aucun endpoint enregistré.</comment>');
            return Command::SUCCESS;
        }

        $table = new Table($output);
        $table->setHeaders(['ID', 'URL', 'Événements', 'Actif']);
        foreach ($items as $w) {
            $table->addRow([
                $w['id'],
                $w['url'],
                implode(', ', $w['events'] ?? []),
                $w['is_active'] ? '<info>oui</info>' : '<comment>non</comment>',
            ]);
        }
        $table->render();

        return Command::SUCCESS;
    }
};

// --- webhook:subscribe --------------------------------------------------------
$webhookSubscribe = new class extends Command {
    protected function configure(): void
    {
        $this->setName('webhook:subscribe')
            ->setDescription('S\'abonner aux événements addon sur un endpoint HTTPS')
            ->addArgument('url', InputArgument::REQUIRED, 'URL HTTPS de votre endpoint')
            ->addOption(
                'events',
                null,
                InputOption::VALUE_OPTIONAL,
                'Événements séparés par virgule',
                'addon.operation.completed,addon.operation.failed,addon.status.changed'
            )
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $events = array_map('trim', explode(',', $input->getOption('events')));
        $result = makeClient()->subscribeToEvents($input->getArgument('url'), $events);

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $status = $result['_status'] ?? 0;
        if ($status === 201) {
            $id = $result['data']['id'] ?? '-';
            $output->writeln("<info>Endpoint enregistré</info> (ID: {$id})");
            $output->writeln("  Événements : " . implode(', ', $events));
        } else {
            $output->writeln("<error>Erreur HTTP {$status}</error>");
            jsonOut($output, $result);
            return Command::FAILURE;
        }

        return Command::SUCCESS;
    }
};

// --- webhook:unsubscribe ------------------------------------------------------
$webhookUnsubscribe = new class extends Command {
    protected function configure(): void
    {
        $this->setName('webhook:unsubscribe')
            ->setDescription('Supprimer un endpoint webhook')
            ->addArgument('id', InputArgument::REQUIRED, 'ID de l\'endpoint')
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $result = makeClient()->unsubscribeFromEvents($input->getArgument('id'));

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $status = $result['_status'] ?? 0;
        $output->writeln($status === 204 || $status === 200
            ? '<info>Endpoint supprimé.</info>'
            : "<error>Erreur HTTP {$status}</error>"
        );

        return ($status === 204 || $status === 200) ? Command::SUCCESS : Command::FAILURE;
    }
};

// --- billing:snapshot ---------------------------------------------------------
$billingSnapshot = new class extends Command {
    protected function configure(): void
    {
        $this->setName('billing:snapshot')
            ->setDescription('Santé financière : wallet + factures impayées + dernières transactions')
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $snapshot = makeClient()->billingSnapshot();

        if ($input->getOption('json')) {
            jsonOut($output, $snapshot);
            return Command::SUCCESS;
        }

        $wallet  = $snapshot['wallet'];
        $balance = $wallet['balance_formatted'] ?? (isset($wallet['balance_cents']) ? eur($wallet['balance_cents']) : '?');
        $output->writeln("<info>Solde wallet :</info> {$balance}");

        $unpaid = $snapshot['unpaid_invoices'];
        if ($unpaid) {
            $output->writeln("\n<comment>Factures impayées :</comment>");
            foreach ($unpaid as $inv) {
                $when = substr((string) ($inv['issued_at'] ?? ''), 0, 10);
                $output->writeln(sprintf(
                    '  %s  %s  %s  (%s)',
                    $inv['invoice_number'] ?? $inv['id'] ?? '?',
                    eur($inv['amount_cents'] ?? 0),
                    $inv['status'] ?? '?',
                    $when
                ));
            }
        } else {
            $output->writeln('<info>Aucune facture impayée.</info>');
        }

        $txs = $snapshot['recent_txs'];
        if ($txs) {
            $output->writeln("\nDernières transactions :");
            $table = new Table($output);
            $table->setHeaders(['Date', 'Description', 'Montant']);
            foreach ($txs as $tx) {
                $table->addRow([
                    substr((string) ($tx['created_at'] ?? '-'), 0, 10),
                    $tx['description']  ?? '-',
                    (($tx['type'] ?? '') === 'credit' ? '+' : '−') . eur($tx['amount'] ?? 0),
                ]);
            }
            $table->render();
        }

        return Command::SUCCESS;
    }
};

// --- token:issue --------------------------------------------------------------
$tokenIssue = new class extends Command {
    protected function configure(): void
    {
        $this->setName('token:issue')
            ->setDescription('Créer un token API')
            ->addArgument('name', InputArgument::REQUIRED, 'Nom du token')
            ->addOption(
                'abilities',
                null,
                InputOption::VALUE_OPTIONAL,
                'Capacités séparées par virgule',
                'billing:read,billing:write'
            )
            ->addOption('json', null, InputOption::VALUE_NONE, 'Sortie JSON brute');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $abilities = array_map('trim', explode(',', $input->getOption('abilities')));
        $result    = makeClient()->issueToken($input->getArgument('name'), $abilities);

        if ($input->getOption('json')) {
            jsonOut($output, $result);
            return Command::SUCCESS;
        }

        $token = $result['token'] ?? $result['plainTextToken'] ?? null;
        if ($token) {
            $output->writeln('<info>Token créé (copiez-le, il ne sera plus affiché) :</info>');
            $output->writeln($token);
        } else {
            jsonOut($output, $result);
        }

        return Command::SUCCESS;
    }
};

// ── Application ───────────────────────────────────────────────────────────────

$app = new Application('bzcapi', '2.0.1');
$app->addCommands([
    $containerList,
    $containerShow,
    $addonList,
    $addonAttach,
    $addonWatch,
    $addonRecover,
    $addonCancel,
    $addonHistory,
    $webhookList,
    $webhookSubscribe,
    $webhookUnsubscribe,
    $billingSnapshot,
    $tokenIssue,
]);

$app->run();
