<?php

declare(strict_types=1);

namespace App\Tag;

use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final readonly class TagVectorSearchClient
{
    /**
     * Minimum similarity score required for a tag to be considered.
     */
    public const MIN_SCORE = 0.72;

    /**
     * Default result size when callers do not specify a limit.
     */
    private const DEFAULT_LIMIT = 8;

    /**
     * Hard limit to prevent excessive requests.
     */
    private const MAX_LIMIT = 50;

    /**
     * HTTP timeout for the Python vector service.
     */
    private const TIMEOUT_SECONDS = 10;

    public function __construct(
        private HttpClientInterface $http,
        private string $serviceUrl,
        private LoggerInterface $agentLogger,
    ) {
    }

    /**
     * Executes a vector search against the Python tag index.
     *
     * Expected response rows:
     * [
     *   {
     *     "tag_id": "...",
     *     "score": 0.73,
     *     "label": "Geräte",
     *     "tag_type": "catalog_entity"
     *   }
     * ]
     *
     * @return list<array{
     *   tag_id:string,
     *   score:float,
     *   label:string,
     *   tag_type:string
     * }>
     */
    public function search(string $query, int $limit = self::DEFAULT_LIMIT): array
    {
        $query = trim($query);

        if ($query === '') {
            return [];
        }

        $limit = max(1, min($limit, self::MAX_LIMIT));
        $serviceUrl = rtrim(trim($this->serviceUrl), '/');

        if ($serviceUrl === '') {
            $this->agentLogger->warning('Tag vector service URL is empty.');

            return [];
        }

        try {
            $response = $this->http->request(
                'POST',
                $serviceUrl . '/search-tags',
                [
                    'json' => [
                        'query' => $query,
                        'limit' => $limit,
                    ],
                    'timeout' => self::TIMEOUT_SECONDS,
                ]
            );

            if ($response->getStatusCode() !== 200) {
                $this->agentLogger->warning(
                    'Tag vector service returned non-200.',
                    ['status' => $response->getStatusCode()]
                );

                return [];
            }

            $data = $response->toArray(false);
        } catch (\Throwable $e) {
            $this->agentLogger->warning(
                'Tag vector service unreachable.',
                ['error' => $e->getMessage()]
            );

            return [];
        }

        if (!is_array($data)) {
            $this->agentLogger->warning('Tag vector service returned invalid payload.');

            return [];
        }

        return $this->normalizeHits($data, $limit);
    }

    /**
     * @param array<mixed> $rows
     * @return list<array{
     *   tag_id:string,
     *   score:float,
     *   label:string,
     *   tag_type:string
     * }>
     */
    private function normalizeHits(array $rows, int $limit): array
    {
        $hitsByTagId = [];

        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }

            $tagId = trim((string) ($row['tag_id'] ?? ''));
            $score = $row['score'] ?? null;

            if ($tagId === '' || !is_numeric($score)) {
                continue;
            }

            $score = (float) $score;

            if ($score < self::MIN_SCORE) {
                continue;
            }

            $normalizedHit = [
                'tag_id' => $tagId,
                'score' => $score,
                'label' => trim((string) ($row['label'] ?? '')),
                'tag_type' => TagTypes::normalize((string) ($row['tag_type'] ?? TagTypes::GENERIC)),
            ];

            $existingHit = $hitsByTagId[$tagId] ?? null;

            if ($existingHit === null || $normalizedHit['score'] > $existingHit['score']) {
                $hitsByTagId[$tagId] = $normalizedHit;
            }
        }

        if ($hitsByTagId === []) {
            return [];
        }

        $hits = array_values($hitsByTagId);

        usort(
            $hits,
            static function (array $left, array $right): int {
                $scoreComparison = $right['score'] <=> $left['score'];

                if ($scoreComparison !== 0) {
                    return $scoreComparison;
                }

                $typeComparison = strcmp($left['tag_type'], $right['tag_type']);

                if ($typeComparison !== 0) {
                    return $typeComparison;
                }

                return strcmp($left['tag_id'], $right['tag_id']);
            }
        );

        return array_slice($hits, 0, $limit);
    }
}