<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Tools;

use Closure;
use Plugin\tnt_mcp\src\Mcp\Analytics\OrderReader;
use Plugin\tnt_mcp\src\Mcp\Contracts\Tool;
use Plugin\tnt_mcp\src\Mcp\Mode\EndpointClass;
use RuntimeException;

/**
 * READ tool: one order in full — header, both addresses, every position.
 *
 * Accepts either the internal id or the order number, because the model will have whichever the
 * shop owner just read out loud, and making it guess which one `orderId` wants is a wasted turn.
 * The positions come from `twarenkorbpos` via `tbestellung.kWarenkorb` — the join nobody gets right
 * on the first try, since there is no `tbestellpos` table despite the name pattern.
 */
final class GetOrder implements Tool
{
    /** @param Closure(): OrderReader $readerFactory must not touch the DB until run() */
    public function __construct(private readonly Closure $readerFactory)
    {
    }

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

    public function description(): string
    {
        return 'Get one order in full: status, totals, payment, shipping, addresses and all line items.';
    }

    /** @return array<string,mixed> */
    public function inputSchema(): array
    {
        return [
            'type'       => 'object',
            'properties' => [
                'orderId'     => [
                    'type'        => 'integer',
                    'minimum'     => 1,
                    'description' => 'Internal order id (tbestellung.kBestellung). '
                        . 'Pass this or orderNumber.',
                ],
                'orderNumber' => [
                    'type'        => 'string',
                    'description' => 'Customer-facing order number (tbestellung.cBestellNr), '
                        . 'as printed on the invoice.',
                ],
            ],
            'additionalProperties' => false,
        ];
    }

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

    public function scope(): string
    {
        return 'analytics:read';
    }

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

    /**
     * @param array<string,mixed> $args
     * @return array<string,mixed>
     */
    public function run(array $args): array
    {
        $id     = $args['orderId'] ?? null;
        $number = $args['orderNumber'] ?? null;
        if ($id !== null && (!\is_int($id) || $id < 1)) {
            throw new RuntimeException('get_order: "orderId" must be a positive whole number.');
        }
        if ($number !== null && !\is_string($number)) {
            throw new RuntimeException('get_order: "orderNumber" must be a string.');
        }
        $number = \is_string($number) ? \trim($number) : null;
        if ($id === null && ($number === null || $number === '')) {
            throw new RuntimeException('get_order: pass either "orderId" or "orderNumber".');
        }

        $order = ($this->readerFactory)()->order($id ?? (string)$number);
        if ($order === null) {
            return [
                'found'  => false,
                'reason' => $id !== null
                    ? 'No order has id ' . $id . '.'
                    : 'No order has number "' . $number . '". Order numbers are exact, not a search — '
                        . 'use search_orders for partial matches.',
            ];
        }

        return ['found' => true] + $order;
    }
}
