<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Admin;

use JTL\DB\DbInterface;
use Plugin\tnt_mcp\src\Mcp\Mode\Mode;
use Plugin\tnt_mcp\src\Mcp\OAuth\DiscoveryMetadata;
use Plugin\tnt_mcp\src\Mcp\OAuth\ScopeCatalog;
use Throwable;

/**
 * Read-only status snapshot for the admin "Server-Status" tab: operating mode, OAuth config
 * presence, token/client counts, a recent-audit outcome summary, plugin/PHP versions and DB
 * reachability. Pure aggregation over the injected {@see DbInterface} — no writes, no shop statics
 * in the hot path — so it is unit-testable with a mock DB. Every DB read is best-effort: a missing
 * table (e.g. before migration) degrades to a zero/false, never an exception.
 */
final class ServerStatusReport
{
    /**
     * @param string $language the admin UI language, used only to label the audit summary — the
     *        rest of this report is numbers and URLs, which have no language.
     */
    public function __construct(private readonly DbInterface $db, private readonly string $language = 'de')
    {
    }

    /**
     * @param non-empty-string $baseUrl absolute shop base URL, used to derive the discovery URLs
     * @return array<string,mixed>
     */
    public function build(Mode $mode, string $pluginVersion, string $baseUrl, bool $oauthKeysPresent): array
    {
        $base = rtrim($baseUrl, '/');

        return [
            'mode'             => $mode->value,
            'modeIsFull'       => $mode === Mode::Full,
            'pluginVersion'    => $pluginVersion,
            'phpVersion'       => \PHP_VERSION,
            'oauthKeysPresent' => $oauthKeysPresent,
            'scopes'           => ScopeCatalog::all(),
            'discovery'        => [
                'authorizationServer' => $base . '/.well-known/oauth-authorization-server',
                'protectedResource'   => $base . '/.well-known/oauth-protected-resource',
                'endpoint'            => $base . '/tnt-mcp',
            ],
            'metadataPreview'  => DiscoveryMetadata::authorizationServer($base),
            'legacyTokens'     => [
                'active' => $this->count('SELECT COUNT(*) AS c FROM tnt_mcp_token WHERE nActive = 1'),
                'total'  => $this->count('SELECT COUNT(*) AS c FROM tnt_mcp_token'),
            ],
            'oauthClients'     => $this->count('SELECT COUNT(*) AS c FROM tnt_mcp_oauth_client'),
            'oauthTokens'      => [
                'active' => $this->count(
                    'SELECT COUNT(*) AS c FROM tnt_mcp_oauth_access_token WHERE nRevoked = 0 AND dExpires > NOW()',
                ),
                'total'  => $this->count('SELECT COUNT(*) AS c FROM tnt_mcp_oauth_access_token'),
            ],
            'auditSummary'     => $this->auditSummary(),
        ];
    }

    private function count(string $sql): int
    {
        try {
            return $this->db->getSingleInt($sql, 'c');
        } catch (Throwable) {
            return 0;
        }
    }

    /**
     * The outcome tally on its own, for the "Aktivität" tab. Public because the tally moved off the
     * status snapshot: it answers "what happened", not "is the server configured".
     *
     * @return list<array{outcome:string,count:int,label:string,tone:string}>
     */
    public function auditOutcomes(): array
    {
        return $this->auditSummary();
    }

    /**
     * Outcome tally over the audit log (ok / denied:* / …), most-common first.
     *
     * The secondary sort is not cosmetic: outcomes that tie on count came back in whatever order
     * the storage engine felt like, so two visits to the same tab showed the same data in a
     * different order and read as different data (docs/UI_REVIEW.md U28).
     *
     * @return list<array{outcome:string,count:int,label:string,tone:string}>
     */
    private function auditSummary(): array
    {
        try {
            $rows = $this->db->getObjects(
                'SELECT cOutcome AS outcome, COUNT(*) AS c
                   FROM tnt_mcp_audit
                  GROUP BY cOutcome
                  ORDER BY c DESC, cOutcome ASC',
            );
        } catch (Throwable) {
            return [];
        }

        $out = [];
        foreach ($rows as $row) {
            $outcome = (string)($row->outcome ?? '');
            $out[]   = [
                'outcome' => $outcome,
                'count'   => (int)($row->c ?? 0),
                'label'   => AuditOutcome::label($outcome, $this->language),
                'tone'    => AuditOutcome::tone($outcome),
            ];
        }

        return $out;
    }
}
