<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Settings;

use JTL\DB\DbInterface;
use JTL\Shop;
use Throwable;

/**
 * The merchant's switch for "an MCP client may configure this shop's LLM provider".
 *
 * ## Why a switch of its own
 *
 * This plugin's `mode` answers "may irreversible things happen?". tnt_workflows'
 * `tnt_wf_snippets_enabled` answers "may AI-authored PHP exist on this shop at all?". Neither
 * answers this one, which is "may a connected client store a credential that SPENDS THE MERCHANT'S
 * MONEY?" — a risk a merchant can reasonably want without the others, or refuse while wanting them.
 *
 * ## Why it now ships ON
 *
 * It shipped OFF, and that default was wrong in practice rather than in theory. Writing the AI
 * configuration already requires an OAuth token carrying `ai:write`, which the admin granted
 * personally on a consent screen that names the scope. The switch was therefore a second consent
 * for an act already consented to, and its only reliable real-world effect was to dead-end the
 * assistant at the exact moment the product promises it can set the shop's AI up — a refusal the
 * merchant then had to resolve in a differently-named pane of a different plugin.
 *
 * The residual risk it guarded is real but narrow: a connected client could repoint `baseUrl` at a
 * hostile OpenAI-compatible endpoint, sending prompt content to an attacker. That is mitigated
 * where it happens rather than by a default nobody understood — every write lands in the change
 * journal, and a non-default `baseUrl` shows a standing notice on the KI tab. The key itself can
 * never be exfiltrated: it is stored encrypted, never read back, and only its last four characters
 * are ever returned.
 *
 * ## What "off" and "absent" mean
 *
 * An explicit off-ish value, or any error reading the row, resolves to OFF — an unreadable setting
 * is not consent. A MISSING row resolves to the declared default (`initialValue="Y"` in info.xml),
 * because the only way to have no row is an install or upgrade that has not written its settings
 * yet; treating that window as "the merchant said no" reintroduces exactly the dead-end above.
 *
 * READ tools (`get_ai_status`, `test_ai_connection`, `list_available_models`) are NOT gated on it:
 * they never reveal the key, only whether one is configured and whether it works, which is what a
 * client needs in order to tell the merchant what to do next.
 *
 * The gate is enforced inside the write tool's `run()`, not by hiding the tool: `tools()` is built
 * without a shop container, and a shop that advertises the tool and refuses it with a message
 * naming the setting teaches the LLM why — whereas a missing tool gets hallucinated around.
 */
final class AiWriteSetting
{
    /** info.xml <ValueName>; also its `cName` in `tplugineinstellungen`. */
    public const VALUE_NAME = 'tnt_mcp_ai_config_enabled';

    /** Stable string PluginID (info.xml <PluginID>). */
    private const PLUGIN_ID = 'tnt_mcp';

    /** What the write tool tells the client when the switch is off — actionable, not "denied". */
    public const DISABLED_HINT = 'Configuring the AI provider over MCP is switched off on this shop. '
        . 'An admin enables it under Plugins → JTL Shop - AI Connector → Einstellungen → "KI darf die '
        . 'KI-Zugangsdaten des Shops setzen", or enters the API key there directly. get_ai_status, '
        . 'test_ai_connection and list_available_models keep working.';

    public static function enabled(?DbInterface $db = null): bool
    {
        try {
            $db  ??= Shop::Container()->getDB();
            $row = $db->getSingleObject(
                'SELECT e.cWert AS v
                   FROM tplugineinstellungen e
                   INNER JOIN tplugin p ON p.kPlugin = e.kPlugin
                  WHERE p.cPluginID = :pid AND e.cName = :n',
                ['pid' => self::PLUGIN_ID, 'n' => self::VALUE_NAME],
            );
        } catch (Throwable) {
            return false;
        }

        if ($row === null || !\is_string($row->v ?? null)) {
            return true;
        }

        return \in_array(\strtolower($row->v), ['y', '1', 'true', 'on'], true);
    }
}
