<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\adminmenu\Controller;

use Plugin\tnt_mcp\src\Ai\AiBudget;
use Plugin\tnt_mcp\src\Ai\AiConfigurationStore;
use Plugin\tnt_mcp\src\Ai\AiStatus;
use Plugin\tnt_mcp\src\Ai\AiStatusStore;
use Plugin\tnt_mcp\src\Ai\AiUsageRepository;
use Plugin\tnt_mcp\src\Mcp\Admin\AdminLanguage;
use Plugin\tnt_mcp\src\Mcp\Admin\ConsentReport;
use Plugin\tnt_mcp\src\Mcp\Admin\IntegrationsReport;
use Plugin\tnt_mcp\src\Mcp\Admin\PermissionAudit;
use Plugin\tnt_mcp\src\Mcp\Admin\PluginProvidersReport;
use Plugin\tnt_mcp\src\Mcp\Admin\ServerStatusReport;
use Plugin\tnt_mcp\src\Mcp\Admin\SetupChecklist;
use Plugin\tnt_mcp\src\Mcp\Admin\TabUrl;
use Plugin\tnt_mcp\src\Mcp\Mode\ModeSetting;
use Plugin\tnt_mcp\src\Mcp\OAuth\KeyManager;
use Throwable;

/**
 * "Start" tab: the one page that answers "what do I do next?" (design §2, step 11).
 *
 * It replaces the two tabs a merchant used to meet first. **Server-Status** opened on the mode
 * badge, the PHP version and a tally of `denied:scope` — every fact true, none of them an answer.
 * **Integrationen** held the thing they actually came for (the endpoint URL and the connect guides)
 * behind a second tab, under a full-width orange rights wall that sat above the primary action.
 *
 * The order of this page is the argument:
 *
 *   1. the checklist, with the FIRST incomplete step as the call to action ({@see SetupChecklist});
 *   2. the endpoint URL, rendered in exactly one place in the whole plugin, with a copy button
 *      (design §6.9 — Server-Status and wf-Onboarding both used to show their own copy of it);
 *   3. the connect guides, one panel open at a time;
 *   4. the rights notice, reduced to one compact row with the full text one disclosure away
 *      (design §6.6) — it belongs BEFORE approving a connection, not above the primary action;
 *   5. the technical facts, collapsed, for the day something is actually broken.
 *
 * Every read is best-effort: this is the first page of the plugin, so a missing table or an
 * unreadable AI record must cost its own section and never the page.
 */
final class Start extends BaseAdminController
{
    public function render(): void
    {
        if (!$this->isAdmin()) {
            $this->notLoggedInNotice();

            return;
        }

        $db       = $this->db();
        $language = $this->language();
        $baseUrl  = $this->baseUrl();
        $consents = $this->consents();
        $warning  = ConsentReport::warning();

        $status = (new ServerStatusReport($db, $language))->build(
            ModeSetting::fromDb($db),
            $this->pluginVersion(),
            $baseUrl,
            $this->keysProvisioned(),
        );

        $steps = SetupChecklist::build(
            $language,
            (bool)($status['oauthKeysPresent'] ?? false),
            $this->liveClients($consents),
            $this->aiState(),
        );

        echo $this->fetch('start.tpl', [
            'steps'           => $steps,
            'nextStep'        => SetupChecklist::nextStep($steps),
            'endpoint'        => (string)($status['discovery']['endpoint'] ?? ''),
            'status'          => $status,
            'providers'       => (new IntegrationsReport($db, $language))->build($baseUrl),
            'pluginProviders' => (new PluginProvidersReport())->build(),
            'consents'        => $consents,
            'warningTitle'    => AdminLanguage::pick($warning['title'], $language),
            'warningText'     => AdminLanguage::pick($warning['text'], $language),
            'rights'          => $this->rights($language),
            // Cross-tab links for the checklist. A step that says "not done" without saying where
            // is a nicer way of saying nothing (SetupChecklist), so the tab it names has to be a
            // real link — built through TabUrl because the tabs are panes of one page.
            'tabUrls'         => [
                'KI'      => $this->tabUrl('KI'),
                'Zugriff' => $this->tabUrl('Zugriff'),
            ],
        ]);
    }

    /** A link to a SIBLING tab of this plugin page; {@see TabUrl} explains `cPluginTab`. */
    private function tabUrl(string $tabName): string
    {
        // The sub-view parameters of the Aktivität tab would otherwise ride along and mean nothing
        // on the target pane.
        return TabUrl::build($_GET, ['tntMcpView' => '', 'tntMcpChange' => ''], $tabName);
    }

    /**
     * OAuth clients holding at least one live token — the checklist asks "is an assistant
     * connected", which is a question about clients, not about how many tokens each one minted.
     *
     * @param list<array{client: string, clientId: string, admin: string, adminKnown: bool,
     *     legacy: bool, superAdmin: bool, scopes: list<string>, expires: string}> $consents
     */
    private function liveClients(array $consents): int
    {
        $ids = [];
        foreach ($consents as $consent) {
            $ids[$consent['clientId']] = true;
        }

        return \count($ids);
    }

    /**
     * @return list<array{client: string, clientId: string, admin: string, adminKnown: bool,
     *     legacy: bool, superAdmin: bool, scopes: list<string>, expires: string}>
     */
    private function consents(): array
    {
        try {
            return (new ConsentReport($this->db()))->liveConsents();
        } catch (Throwable) {
            return [];
        }
    }

    /**
     * The AI verdict for the checklist's optional row, from the STORED signal only.
     *
     * The KI tab spends a provider call to refresh a stale record ({@see Ai::status()}); this page
     * must not — it is the landing page, it is opened by merchants who never configured an LLM, and
     * a checklist row is not worth a network round trip on every visit.
     *
     * `null` means "cannot be read", which {@see SetupChecklist} treats like "not configured": the
     * row stays optional rather than inventing an outstanding task.
     */
    private function aiState(): ?string
    {
        try {
            $db     = $this->db();
            $config = (new AiConfigurationStore($db))->load();
            $usage  = new AiUsageRepository($db);

            return AiStatus::evaluate(
                $config,
                (new AiStatusStore($db))->load(),
                AiBudget::fromSettings($db),
                $usage->spentTodayMicros(),
                $usage->runsThisHour(),
            )->state;
        } catch (Throwable) {
            return null;
        }
    }

    private function keysProvisioned(): bool
    {
        try {
            return KeyManager::default()->isProvisioned();
        } catch (Throwable) {
            return false;
        }
    }

    /**
     * The Q2 rights finding for the admin viewing the page. `null` when it cannot be produced (a
     * broken/unreadable catalog must not take the page down).
     *
     * @return array{ok: bool, login: string, missing: list<string>, text: string}|null
     */
    private function rights(string $language): ?array
    {
        try {
            $admin = PermissionAudit::currentAdmin();
            if ($admin === null) {
                return null;
            }
            $report = PermissionAudit::forShippedCatalog()
                ->report($admin['login'], $admin['groupId'], $admin['permissions']);

            return [
                'ok'      => $report['ok'],
                'login'   => $report['login'],
                'missing' => $report['missing'],
                'text'    => AdminLanguage::pick($report['text'], $language),
            ];
        } catch (Throwable) {
            return null;
        }
    }
}
