<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Marketing\Emailing;

use CpCreation\VitiCore\Marketing\Model\BrevoConfiguration;
use CpCreation\VitiCore\Marketing\Repository\BrevoConfigurationRepository;
use CpCreation\VitiCore\Monitoring\External\ExternalConnectionHealthReporter;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final readonly class BrevoClient
{
    private const string BASE_URL = 'https://api.brevo.com/v3';

    public function __construct(
        private HttpClientInterface $httpClient,
        private BrevoConfigurationRepository $configurationRepository,
        private BrevoSecretCipher $secretCipher,
        private ExternalConnectionHealthReporter $healthReporter,
    ) {}

    public function account(): array
    {
        return $this->request('GET', '/account');
    }

    public function domains(): array
    {
        return $this->request('GET', '/senders/domains');
    }

    public function domain(string $domain): array
    {
        return $this->request('GET', '/senders/domains/' . rawurlencode($domain));
    }

    public function createDomain(string $domain): array
    {
        return $this->request('POST', '/senders/domains', ['json' => ['name' => $domain]]);
    }

    public function authenticateDomain(string $domain): array
    {
        return $this->request('PUT', '/senders/domains/' . rawurlencode($domain) . '/authenticate');
    }

    public function senders(): array
    {
        return $this->request('GET', '/senders');
    }

    public function folders(): array
    {
        return $this->request('GET', '/contacts/folders', ['query' => ['limit' => 50]]);
    }

    public function lists(): array
    {
        return $this->request('GET', '/contacts/lists', ['query' => ['limit' => 50, 'sort' => 'desc']]);
    }

    public function createList(string $name, int $folderId): array
    {
        return $this->request('POST', '/contacts/lists', ['json' => [
            'name' => $name,
            'folderId' => $folderId,
        ]]);
    }

    public function updateList(int $listId, string $name): void
    {
        $this->request('PUT', '/contacts/lists/' . $listId, ['json' => ['name' => $name]]);
    }

    public function deleteList(int $listId): void
    {
        $this->request('DELETE', '/contacts/lists/' . $listId);
    }

    /** @param list<array{email: string, attributes?: array<string, string>}> $contacts */
    public function importContacts(int $listId, array $contacts): array
    {
        return $this->request('POST', '/contacts/import', ['json' => [
            'listIds' => [$listId],
            'jsonBody' => $contacts,
            'updateExistingContacts' => true,
            'emptyContactsAttributes' => false,
            'disableNotification' => false,
        ]]);
    }

    public function importCsv(int $listId, string $csv): array
    {
        return $this->request('POST', '/contacts/import', ['json' => [
            'listIds' => [$listId],
            'fileBody' => $csv,
            'updateExistingContacts' => true,
            'emptyContactsAttributes' => false,
            'disableNotification' => false,
        ]]);
    }

    public function createCampaign(array $payload): array
    {
        return $this->request('POST', '/emailCampaigns', ['json' => $payload]);
    }

    public function sendCampaignNow(int $campaignId): void
    {
        $this->request('POST', '/emailCampaigns/' . $campaignId . '/sendNow');
    }

    private function request(string $method, string $path, array $options = []): array
    {
        $configuration = $this->readyConfiguration();
        $startedAt = microtime(true);
        $options['headers'] = array_merge($options['headers'] ?? [], [
            'Accept' => 'application/json',
            'Content-Type' => 'application/json',
            'api-key' => $configuration->revealApiKey($this->secretCipher),
        ]);
        $options['timeout'] = 15;
        $options['max_duration'] = 20;
        $options['max_redirects'] = 0;

        try {
            $response = $this->httpClient->request($method, self::BASE_URL . $path, $options);
            $statusCode = $response->getStatusCode();
            $body = $response->getContent(false);
            if ($statusCode < 200 || $statusCode >= 300) {
                throw new BrevoApiException($this->errorMessage($body, $statusCode), $statusCode);
            }
            $data = '' === $body ? [] : json_decode($body, true, 64, \JSON_THROW_ON_ERROR);
            if (!\is_array($data)) {
                throw new BrevoApiException('Brevo a renvoyé une réponse inattendue.', $statusCode);
            }

            if ('/account' === $path) {
                $configuration->markHealthy(
                    \is_string($data['email'] ?? null) ? $data['email'] : null,
                    \is_string($data['companyName'] ?? null) ? $data['companyName'] : null,
                );
            } else {
                $configuration->markHealthy(
                    $configuration->getAccountEmail(),
                    $configuration->getCompanyName(),
                );
            }
            $this->configurationRepository->save($configuration);
            $this->healthReporter->report('brevo', true, $this->latency($startedAt));

            return $data;
        } catch (BrevoApiException $exception) {
            $this->recordFailure($configuration, $exception->getMessage(), $startedAt);
            throw $exception;
        } catch (TransportExceptionInterface|\JsonException $exception) {
            $message = $exception instanceof \JsonException
                ? 'Brevo a renvoyé une réponse JSON invalide.'
                : 'La connexion réseau avec Brevo a échoué.';
            $this->recordFailure($configuration, $message, $startedAt);
            throw new BrevoApiException($message, previous: $exception);
        }
    }

    private function readyConfiguration(): BrevoConfiguration
    {
        $configuration = $this->configurationRepository->get();
        if (null === $configuration || !$configuration->isEnabled() || !$configuration->hasApiKey()) {
            throw new BrevoApiException('La connexion Brevo n’est pas activée ou configurée.');
        }

        return $configuration;
    }

    private function recordFailure(BrevoConfiguration $configuration, string $message, float $startedAt): void
    {
        $configuration->markFailure($message);
        $this->configurationRepository->save($configuration);
        $this->healthReporter->report('brevo', false, $this->latency($startedAt), $message);
    }

    private function latency(float $startedAt): int
    {
        return (int) round((microtime(true) - $startedAt) * 1000);
    }

    private function errorMessage(string $body, int $statusCode): string
    {
        try {
            $data = json_decode($body, true, 16, \JSON_THROW_ON_ERROR);
            $detail = \is_array($data) && \is_string($data['message'] ?? null)
                ? mb_substr($data['message'], 0, 500)
                : null;
        } catch (\JsonException) {
            $detail = null;
        }

        return null === $detail
            ? \sprintf('Brevo a répondu avec le statut HTTP %d.', $statusCode)
            : \sprintf('Brevo a répondu avec le statut HTTP %d : %s', $statusCode, $detail);
    }
}
