<?php

/*
 * This file is part of the FOSElasticaBundle package.
 *
 * (c) FriendsOfSymfony <https://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace KupShop\CatalogElasticBundle\Util;

use Elastic\Elasticsearch\Exception\ClientResponseException;
use Elastic\Elasticsearch\Exception\ServerResponseException;
use Elastic\Elasticsearch\Response\Elasticsearch;
use Elastic\Transport\Exception\NoNodeAvailableException;
use Elastica\Client as BaseClient;
use KupShop\CatalogElasticBundle\Exception\ElasticSearchUnavailableException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
 * Extends the default Elastica client to provide logging for errors that occur
 * during communication with ElasticSearch.
 *
 * @author Gordon Franke <info@nevalon.de>
 */
class Client extends BaseClient
{
    public const ELASTIC_UNAVAILABLE_KEY = 'elasticUnavailable';
    private const ELASTIC_UNAVAILABLE_BACKOFF_DURATION = 30;

    #[\Override]
    public function sendRequest(RequestInterface $request): Elasticsearch
    {
        try {
            $response = parent::sendRequest($request);
        } catch (NoNodeAvailableException|ServerResponseException|ClientResponseException $e) {
            $this->markElasticAsUnavailable();
            throw new ElasticSearchUnavailableException($e->getMessage(), previous: $e);
        }

        if (isLocalDevelopment()) {
            $this->doLogRequest($request, $response);
        }

        return $response;
    }

    private function markElasticAsUnavailable()
    {
        $var = 1;
        setCache(self::ELASTIC_UNAVAILABLE_KEY, $var, self::ELASTIC_UNAVAILABLE_BACKOFF_DURATION);
    }

    private function doLogRequest(RequestInterface $request, ResponseInterface $response): void
    {
        $responseBody = json_decode((string) $response->getBody(), true) ?? [];

        $took = $responseBody['took'] ?? 0;
        $hits = $responseBody['hits']['total']['value'] ?? 0;
        if (isset($responseBody['responses'])) {
            $hits = [];
            foreach ($responseBody['responses'] as $subResponse) {
                $hits[] = $subResponse['hits']['total']['value'] ?? 0;
            }

            $hits = implode(',', $hits);
        }
        $hosts = array_map(fn ($host) => parse_url($host), $this->_config->get('hosts'));

        $connectionArray = [
            'host' => implode(',', array_column($hosts, 'host')),
            'port' => implode(',', array_column($hosts, 'port')),
            'transport' => 'http?',
            'headers' => $request->getHeaders(),
        ];

        $this->_logger->logQuery(
            path: $request->getUri()->getPath(),
            method: $request->getMethod(),
            response: (string) $response->getBody(),
            data: (string) $request->getBody(),
            queryTime: 0,
            connection: $connectionArray,
            query: array_filter(explode('&', str_replace('?', '', $request->getUri()->getQuery()))),
            engineTime: $took,
            itemCount: $hits,
        );
    }
}
