<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Settings;

use InvalidArgumentException;
use JTL\DB\DbInterface;
use JTL\Plugin\Helper;
use Throwable;

/**
 * Writes one plugin setting (`tplugineinstellungen`) the same way JTL's own plugin settings form
 * does, so a switch flipped on the onboarding tab and the same switch flipped on the plugin's
 * settings page end up in an identical state.
 *
 * "The same way" is literal and load-bearing, taken from
 * `JTL\Router\Controller\Backend\PluginController::actionConfig()`:
 * 1. DELETE then INSERT rather than UPDATE — a setting that has never been saved has no row at all,
 *    and an UPDATE would silently write nothing on exactly the fresh install this tab exists for.
 * 2. Re-init the plugin with the cache invalidated and refresh the running instance. Without that
 *    the shop keeps serving the cached `Plugin` object and the settings PAGE would still show the
 *    old value even though the gates already read the new one — the sort of split state that makes
 *    a merchant flip a switch twice.
 *
 * Note the gates themselves ({@see \Plugin\tnt_mcp\src\Settings\AiWriteSetting},
 * {@see \Plugin\tnt_mcp\src\Mcp\Mode\ModeSetting}) read the row directly on every call and are
 * therefore correct the instant step 1 commits; step 2 is for the admin UI, not for enforcement.
 *
 * The value is written verbatim. This writer is not a validator: the caller owns the vocabulary of
 * its own setting, and an allowlist here would be a second place to keep in sync with info.xml.
 * What it does refuse is an unknown plugin — that would create an orphan row keyed to nothing.
 */
final class PluginSettingWriter
{
    public function __construct(private readonly DbInterface $db)
    {
    }

    /**
     * @param string $pluginId stable string PluginID (info.xml `<PluginID>`)
     * @param string $name     the setting's `<ValueName>`
     *
     * @throws InvalidArgumentException when the setting name is empty or no plugin with that
     *                                  PluginID is installed
     */
    public function set(string $pluginId, string $name, string $value): void
    {
        if ($name === '') {
            throw new InvalidArgumentException('Setting name must not be empty.');
        }

        $kPlugin = $this->resolve($pluginId);
        if ($kPlugin === 0) {
            throw new InvalidArgumentException('Unknown plugin: ' . $pluginId);
        }

        $this->db->delete('tplugineinstellungen', ['kPlugin', 'cName'], [$kPlugin, $name]);
        $this->db->insert('tplugineinstellungen', (object)[
            'kPlugin' => $kPlugin,
            'cName'   => $name,
            'cWert'   => $value,
        ]);

        $this->refresh($kPlugin);
    }

    private function resolve(string $pluginId): int
    {
        try {
            $row = $this->db->getSingleObject(
                'SELECT `kPlugin` FROM `tplugin` WHERE `cPluginID` = :pid LIMIT 1',
                ['pid' => $pluginId],
            );
        } catch (Throwable) {
            return 0;
        }

        return (int)($row->kPlugin ?? 0);
    }

    /**
     * Best-effort: the setting is already stored and every gate already reads it. A cache that could
     * not be refreshed makes the admin form stale until the next plugin reload — annoying, not
     * wrong — and is not worth failing a write the merchant just made.
     */
    private function refresh(int $kPlugin): void
    {
        try {
            $plugin = Helper::getLoaderByPluginID($kPlugin, $this->db)->init($kPlugin, true);
            if ($plugin->isBootstrap()) {
                Helper::updatePluginInstance($plugin);
            }
        } catch (Throwable) {
            // see the doc block
        }
    }
}
