<?php

namespace App\Service;

use App\Message\MeilisearchMessage;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class MeilisearchService
{
    public function __construct(
        private HttpClientInterface $httpClient,
        private MessageBusInterface $bus,
        #[Autowire(env: 'MEILISEARCH_URL')] private string $url,
        #[Autowire(env: 'MEILISEARCH_API_KEY')] private string $apiKey,
        #[Autowire(service: 'meilisearch.cache')] private CacheInterface&CacheItemPoolInterface $cache,
    ) {
    }

    public function indexExists(string $index): bool
    {
        try {
            $response = $this->httpClient->request('GET', $this->url."/indexes/{$index}", [
                'headers' => ['Authorization' => "Bearer {$this->apiKey}"],
            ]);

            return 200 === $response->getStatusCode();
        } catch (\Throwable) {
            return false;
        }
    }

    public function createIndexIfNotExists(string $index, string $primaryKey = 'id'): void
    {
        if (!$this->indexExists($index)) {
            $this->createIndex($index, $primaryKey);
        }
    }

    public function createIndex(string $index, string $primaryKey = 'id'): void
    {
        $this->bus->dispatch(new MeilisearchMessage('createIndex', $index, ['primaryKey' => $primaryKey]));
    }

    public function deleteIndex(string $index): void
    {
        $this->bus->dispatch(new MeilisearchMessage('deleteIndex', $index));
    }

    /**
     * @param list<array<string, mixed>> $documents
     */
    public function addDocuments(string $index, array $documents): void
    {
        $this->bus->dispatch(new MeilisearchMessage('addDocuments', $index, ['documents' => $documents]));
    }

    /**
     * @param list<array<string, mixed>> $documents
     */
    public function updateDocuments(string $index, array $documents): void
    {
        $this->bus->dispatch(new MeilisearchMessage('updateDocuments', $index, ['documents' => $documents]));
    }

    public function deleteDocument(string $index, string|int $documentId): void
    {
        $this->bus->dispatch(new MeilisearchMessage('deleteDocument', $index, ['documentId' => $documentId]));
    }

    /**
     * @param list<string|int> $ids
     */
    public function deleteDocuments(string $index, array $ids): void
    {
        $this->bus->dispatch(new MeilisearchMessage('deleteDocuments', $index, ['ids' => $ids]));
    }

    /**
     * @param array<string, mixed> $settings
     */
    public function updateSettings(string $index, array $settings): void
    {
        $this->bus->dispatch(new MeilisearchMessage('updateSettings', $index, ['settings' => $settings]));
    }

    /**
     * @param array<string, mixed> $options
     *
     * @return array<string, mixed>
     */
    public function search(string $index, string $query, array $options = []): array
    {
        $cacheKey = 'ms_search_'.hash('xxh128', $index.$query.serialize($options));

        return $this->cache->get($cacheKey, function (ItemInterface $item) use ($index, $query, $options) {
            $item->expiresAfter(300);

            return $this->request('POST', "/indexes/{$index}/search", array_merge([
                'q' => $query,
            ], $options));
        });
    }

    public function invalidateSearchCache(): void
    {
        $this->cache->clear();
    }

    /**
     * @return array<string, mixed>
     */
    public function getDocument(string $index, string|int $documentId): array
    {
        return $this->request('GET', "/indexes/{$index}/documents/{$documentId}");
    }

    /**
     * @return list<int|string>
     */
    public function getAllDocumentIds(string $index): array
    {
        $ids = [];
        $offset = 0;
        $limit = 1000;

        do {
            $response = $this->request('GET', "/indexes/{$index}/documents?offset={$offset}&limit={$limit}&fields=id");
            $results = $response['results'] ?? [];
            foreach ($results as $doc) {
                $ids[] = $doc['id'];
            }
            $offset += $limit;
        } while (\count($results) === $limit);

        return $ids;
    }

    /**
     * @return list<string>
     */
    public function listIndexes(): array
    {
        $response = $this->request('GET', '/indexes?limit=1000');
        $indexes = [];
        foreach ($response['results'] ?? [] as $idx) {
            $indexes[] = $idx['uid'];
        }

        return $indexes;
    }

    /**
     * @param array<string, mixed>|null $body
     *
     * @return array<string, mixed>
     */
    public function request(string $method, string $path, ?array $body = null): array
    {
        $options = [
            'headers' => [
                'Authorization' => "Bearer {$this->apiKey}",
                'Content-Type' => 'application/json',
            ],
        ];

        if (null !== $body && 'GET' !== $method && 'DELETE' !== $method) {
            $options['json'] = $body;
        }

        $response = $this->httpClient->request($method, $this->url.$path, $options);

        if (204 === $response->getStatusCode()) {
            return [];
        }

        return $response->toArray(false);
    }
}
