<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\Migrations;

use JTL\Plugin\Migration;
use JTL\Update\IMigration;
use Plugin\tnt_mcp\src\Mcp\OAuth\KeyManager;
use Plugin\tnt_mcp\src\Mcp\OAuth\KeyStore;
use Throwable;

/**
 * Moves the OAuth key material off the filesystem and into the database, and gives dynamic client
 * registration something to rate-limit on.
 *
 * **Why.** `plugins/tnt_mcp/keys/` sits in the document root. Its `.htaccess` is honoured by Apache
 * and ignored by nginx, so on an nginx shop the RSA private key signing every access token was
 * fetchable over HTTP. Rows in `tnt_mcp_oauth_key` are encrypted with a key derived from the
 * shop's `BLOWFISH_KEY` ({@see KeyStore}) and no web server serves them by accident.
 *
 * **The existing keys are adopted, not replaced** — {@see KeyManager::importLegacyFiles()} copies
 * each file into the store and deletes it, so tokens issued before the upgrade keep verifying and
 * no connector has to re-consent. A shop with no `keys/` dir yet (never provisioned) simply gets
 * the table.
 *
 * `cRegistrarIp` on the client table records who registered a connector, which is what
 * {@see \Plugin\tnt_mcp\src\Mcp\OAuth\ClientRegistrar} caps per day — the endpoint is
 * unauthenticated by RFC 7591 and was previously an unbounded INSERT.
 *
 * Idempotent: `CREATE TABLE IF NOT EXISTS`, and the column is added only when absent.
 */
class Migration20260805000000 extends Migration implements IMigration
{
    public function up(): void
    {
        $this->execute('CREATE TABLE IF NOT EXISTS `' . KeyStore::TABLE . '` (
                `cName` VARCHAR(32) NOT NULL,
                `cValue` MEDIUMTEXT NOT NULL,
                `dCreated` DATETIME NOT NULL,
                PRIMARY KEY (`cName`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

        if (!$this->hasColumn('tnt_mcp_oauth_client', 'cRegistrarIp')) {
            $this->execute(
                'ALTER TABLE `tnt_mcp_oauth_client` '
                . "ADD COLUMN `cRegistrarIp` VARCHAR(45) NOT NULL DEFAULT '', "
                . 'ADD KEY `idx_tnt_mcp_oauth_client_ip` (`cRegistrarIp`, `dCreated`)'
            );
        }

        // Best-effort: an install whose keys cannot be adopted (unreadable file, BLOWFISH_KEY
        // missing) must still end the migration with the table in place. The next OAuth request
        // then provisions a fresh keypair — connectors re-consent, which beats a failed update.
        try {
            (new KeyManager(KeyStore::default($this->getDB()), KeyManager::defaultLegacyDir()))
                ->importLegacyFiles();
        } catch (Throwable) {
            // intentionally ignored — see above
        }
    }

    public function down(): void
    {
        $this->execute('DROP TABLE IF EXISTS `' . KeyStore::TABLE . '`');
        if ($this->hasColumn('tnt_mcp_oauth_client', 'cRegistrarIp')) {
            $this->execute('ALTER TABLE `tnt_mcp_oauth_client` DROP COLUMN `cRegistrarIp`');
        }
    }

    /**
     * `ADD COLUMN IF NOT EXISTS` is MariaDB-only and JTL supports MySQL too, so the check is a
     * lookup rather than a dialect gamble.
     */
    private function hasColumn(string $table, string $column): bool
    {
        return $this->getDB()->getSingleObject(
            'SELECT COLUMN_NAME FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t AND COLUMN_NAME = :c',
            ['t' => $table, 'c' => $column],
        ) !== null;
    }
}
