<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Tools;

use InvalidArgumentException;
use Plugin\tnt_mcp\src\Ai\AiConfigurationStore;
use Plugin\tnt_mcp\src\Ai\AiSetupTokenStore;
use Plugin\tnt_mcp\src\Enums\AiProviders;
use Plugin\tnt_mcp\src\Mcp\Admin\PluginAdminUrl;
use Plugin\tnt_mcp\src\Mcp\Contracts\Tool;
use Plugin\tnt_mcp\src\Mcp\Mode\EndpointClass;
use Plugin\tnt_mcp\src\Settings\AiWriteSetting;
use RuntimeException;

/**
 * WRITE tool: hand the merchant a short-lived admin link on which THEY type the API key.
 *
 * ## Why the key cannot simply be passed to `set_ai_configuration`
 *
 * It can — that tool still accepts one, for a merchant driving an assistant they run themselves. But
 * an assistant should never ASK for it. A key pasted into a chat window has been sent to whoever
 * hosts that assistant, logged wherever that conversation is logged, and is now a credential the
 * merchant cannot un-share. This tool is the answer an assistant gives instead of asking: the
 * provider and model it already knows are written for the merchant, and the one field the merchant
 * must supply themselves gets a link that focuses it.
 *
 * ## The link is not a bypass
 *
 * Opening it requires a logged-in admin session like any other `/admin` URL; the token only proves
 * "an assistant prepared this" and is spent on first use. See {@see AiSetupTokenStore} for why that
 * makes a leaked token uninteresting.
 *
 * ## Why it is a write
 *
 * It writes the prefill and it mints a row, so it belongs in the audit trail beside every other
 * configuration touch. It is {@see EndpointClass::Reversible} because the merchant can change
 * everything it wrote from the very form it opens.
 */
final class BeginAiKeyEntry implements Tool
{
    /** @var \Closure(): AiConfigurationStore */
    private readonly \Closure $store;

    /** @var \Closure(): AiSetupTokenStore */
    private readonly \Closure $tokens;

    /** @var \Closure(): bool */
    private readonly \Closure $enabled;

    /** @var \Closure(string): string */
    private readonly \Closure $url;

    /**
     * @param \Closure(): AiConfigurationStore   $store
     * @param (\Closure(): AiSetupTokenStore)|null $tokens
     * @param (\Closure(): bool)|null              $enabled  the merchant's switch, lazily read
     * @param (\Closure(string): string)|null      $url      token => absolute admin link
     */
    public function __construct(
        \Closure $store,
        ?\Closure $tokens = null,
        ?\Closure $enabled = null,
        ?\Closure $url = null,
    ) {
        $this->store   = $store;
        $this->tokens  = $tokens ?? static fn (): AiSetupTokenStore => new AiSetupTokenStore();
        $this->enabled = $enabled ?? static fn (): bool => AiWriteSetting::enabled();
        $this->url     = $url ?? static fn (string $token): string
            => PluginAdminUrl::tab('KI', ['entry' => $token]);
    }

    public function name(): string
    {
        return 'begin_ai_key_entry';
    }

    public function description(): string
    {
        return 'Create a short-lived admin link where the merchant types the API key themselves. '
            . 'Keys can never be passed through this connection.';
    }

    /** @return array<string,mixed> */
    public function inputSchema(): array
    {
        return [
            'type'       => 'object',
            'properties' => [
                'provider' => [
                    'type'        => 'string',
                    'enum'        => AiProviders::values(),
                    'description' => 'Prefill the form with this provider. Omit to leave whatever '
                        . 'is configured.',
                ],
                'model' => [
                    'type'        => 'string',
                    'description' => 'Prefill the default model, e.g. gpt-4.1-mini. Only used '
                        . 'together with provider.',
                ],
                'baseUrl' => [
                    'type'        => 'string',
                    'description' => 'Prefill the API root. Required for provider "compatible"; '
                        . 'leave empty for the provider default.',
                ],
            ],
            'additionalProperties' => false,
        ];
    }

    public function isWrite(): bool
    {
        return true;
    }

    public function scope(): string
    {
        return 'ai:write';
    }

    public function endpointClass(): EndpointClass
    {
        return EndpointClass::Reversible;
    }

    /**
     * @param array<string,mixed> $args
     *
     * @return array<string,mixed>
     */
    public function run(array $args): array
    {
        if (!($this->enabled)()) {
            return [
                'ok'      => false,
                'refused' => 'disabled',
                'error'   => AiWriteSetting::DISABLED_HINT,
            ];
        }

        $prefilled = false;
        if (($args['provider'] ?? '') !== '') {
            $provider = AiProviders::tryFrom((string)$args['provider']);
            if ($provider === null) {
                throw new InvalidArgumentException(
                    'provider must be one of: ' . \implode(', ', AiProviders::values())
                );
            }

            try {
                ($this->store)()->prefill(
                    $provider,
                    (string)($args['model'] ?? ''),
                    \trim((string)($args['baseUrl'] ?? ''))
                );
                $prefilled = true;
            } catch (InvalidArgumentException | RuntimeException $e) {
                // The link is still worth issuing — the merchant can pick the provider on the form —
                // but the assistant has to know its prefill did not land.
                return [
                    'ok'      => false,
                    'refused' => 'invalid-argument',
                    'error'   => $e->getMessage(),
                ];
            }
        }

        $issued = ($this->tokens)()->issue('mcp');
        if ($issued === null) {
            return [
                'ok'      => false,
                'refused' => 'unavailable',
                'error'   => 'The setup link could not be created on this shop. Ask the merchant to '
                    . 'open Plugins → JTL Shop - AI Connector → KI and enter the key there.',
            ];
        }

        $url = ($this->url)($issued['token']);

        return [
            'ok'          => true,
            'url'         => $url === '' ? null : $url,
            'expiresAt'   => $issued['expiresAt'],
            'prefilled'   => $prefilled,
            'instruction' => ($url === ''
                ? 'This shop could not resolve its own admin URL, so ask the merchant to open '
                    . 'Plugins → JTL Shop - AI Connector → KI themselves. '
                : 'Give the merchant this link. It opens the shop-admin AI form with everything '
                    . 'prefilled except the API key, which they must paste themselves. The link '
                    . 'expires in 15 minutes and works once; they must already be logged into the '
                    . 'shop admin. ')
                . 'Afterwards call get_ai_status with checkConnection true.',
        ];
    }
}
