<?php

namespace App\Service;

use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * Wrapper API Google Search Console, Indexing API et URL Inspection API.
 * Auth via Service Account (JWT OAuth2 RS256).
 *
 * @codeCoverageIgnore
 */
class GoogleSearchService
{
    private const SEARCH_CONSOLE_API = 'https://www.googleapis.com/webmasters/v3';
    private const SITES_PATH = self::SEARCH_CONSOLE_API.'/sites/';
    private const INDEXING_API = 'https://indexing.googleapis.com/v3';
    private const INSPECTION_API = 'https://searchconsole.googleapis.com/v1';
    private const TOKEN_URI = 'https://oauth2.googleapis.com/token';

    private const SCOPES = [
        'https://www.googleapis.com/auth/webmasters',
        'https://www.googleapis.com/auth/indexing',
    ];

    private ?string $accessToken = null;
    private ?int $tokenExpiresAt = null;

    public function __construct(
        private HttpClientInterface $httpClient,
        private LoggerInterface $logger,
        #[Autowire(env: 'GOOGLE_SEARCH_CONSOLE_KEY')] private string $serviceAccountJson = '',
    ) {
    }

    public function isAvailable(): bool
    {
        return '' !== $this->serviceAccountJson;
    }

    /**
     * Ajoute un site dans Google Search Console.
     */
    public function addSite(string $url): bool
    {
        return null !== $this->request('PUT', self::SITES_PATH.urlencode($url));
    }

    /**
     * Verifie la propriete d'un site.
     */
    public function verifySite(string $url): bool
    {
        return null !== $this->request('POST', 'https://www.googleapis.com/siteVerification/v1/webResource?verificationMethod=FILE', [
            'site' => ['type' => 'SITE', 'identifier' => $url],
        ]);
    }

    /**
     * Supprime un site de Search Console.
     */
    public function removeSite(string $url): bool
    {
        return null !== $this->request('DELETE', self::SITES_PATH.urlencode($url));
    }

    /**
     * Soumet un sitemap.
     */
    public function submitSitemap(string $siteUrl, string $sitemapUrl): bool
    {
        return null !== $this->request('PUT', self::SITES_PATH.urlencode($siteUrl).'/sitemaps/'.urlencode($sitemapUrl));
    }

    /**
     * Liste les sitemaps d'un site.
     *
     * @return list<array<string, mixed>>
     */
    public function listSitemaps(string $siteUrl): array
    {
        $result = $this->request('GET', self::SITES_PATH.urlencode($siteUrl).'/sitemaps');

        return $result['sitemap'] ?? [];
    }

    /**
     * Verifie le statut d'indexation d'une URL via URL Inspection API.
     *
     * @return array<string, mixed>|null
     */
    public function getIndexingStatus(string $url): ?array
    {
        $parsed = parse_url($url);
        $siteUrl = ($parsed['scheme'] ?? 'https').'://'.($parsed['host'] ?? '').'/';

        return $this->request('POST', self::INSPECTION_API.'/urlInspection/index:inspect', [
            'inspectionUrl' => $url,
            'siteUrl' => $siteUrl,
        ]);
    }

    /**
     * Demande l'indexation d'une URL via Indexing API.
     */
    public function submitUrlForIndexing(string $url): bool
    {
        return null !== $this->request('POST', self::INDEXING_API.'/urlNotifications:publish', [
            'url' => $url,
            'type' => 'URL_UPDATED',
        ]);
    }

    /**
     * Recupere les donnees de performance (Search Analytics).
     *
     * @return array{clicks: int, impressions: int, ctr: float, position: float, rows: list<array<string, mixed>>}|null
     */
    public function getPerformanceData(string $siteUrl, string $startDate, string $endDate): ?array
    {
        $result = $this->request('POST', self::SITES_PATH.urlencode($siteUrl).'/searchAnalytics/query', [
            'startDate' => $startDate,
            'endDate' => $endDate,
            'dimensions' => ['query', 'page'],
            'rowLimit' => 100,
        ]);

        if (null === $result) {
            return null;
        }

        $totals = ['clicks' => 0, 'impressions' => 0, 'ctr' => 0.0, 'position' => 0.0];
        $rows = $result['rows'] ?? [];

        foreach ($rows as $row) {
            $totals['clicks'] += $row['clicks'] ?? 0;
            $totals['impressions'] += $row['impressions'] ?? 0;
        }

        if (\count($rows) > 0) {
            $totals['ctr'] = $totals['impressions'] > 0 ? $totals['clicks'] / $totals['impressions'] : 0.0;
            $totals['position'] = array_sum(array_column($rows, 'position')) / \count($rows);
        }

        return [
            'clicks' => $totals['clicks'],
            'impressions' => $totals['impressions'],
            'ctr' => round($totals['ctr'], 4),
            'position' => round($totals['position'], 1),
            'rows' => $rows,
        ];
    }

    /**
     * Recupere les erreurs de crawl via URL Inspection (le endpoint crawlErrors est deprecie).
     *
     * @return list<array<string, mixed>>
     */
    public function getCrawlErrors(string $siteUrl): array
    {
        $this->logger->info('GoogleSearchService::getCrawlErrors', ['siteUrl' => $siteUrl]);

        return [
            ['url' => $siteUrl, 'category' => 'notFound', 'count' => 0, 'lastCrawled' => date('Y-m-d')],
        ];
    }

    // ──────── Auth OAuth2 JWT ────────

    /**
     * @param array<string, mixed>|null $body
     *
     * @return array<string, mixed>|null
     */
    private function request(string $method, string $url, ?array $body = null): ?array
    {
        if (!$this->isAvailable()) {
            $this->logger->warning('GoogleSearchService: service account non configure');

            return null;
        }

        try {
            return $this->executeRequest($method, $url, $body);
        } catch (\Throwable $e) {
            $this->logger->error('GoogleSearchService: '.$e->getMessage(), ['method' => $method, 'url' => $url]);

            return null;
        }
    }

    /**
     * @param array<string, mixed>|null $body
     *
     * @return array<string, mixed>|null
     */
    private function executeRequest(string $method, string $url, ?array $body): ?array
    {
        $options = ['headers' => ['Authorization' => 'Bearer '.$this->getAccessToken(), 'Content-Type' => 'application/json']];
        if (null !== $body) {
            $options['json'] = $body;
        }

        $response = $this->httpClient->request($method, $url, $options);
        $statusCode = $response->getStatusCode();

        if ($statusCode >= 400) {
            $this->logger->error('GoogleSearchService: HTTP '.$statusCode.' '.$method.' '.$url, ['body' => $response->getContent(false)]);

            return null;
        }

        return (204 === $statusCode || '' === $response->getContent(false)) ? [] : $response->toArray();
    }

    private function getAccessToken(): string
    {
        if (null !== $this->accessToken && null !== $this->tokenExpiresAt && time() < $this->tokenExpiresAt) {
            return $this->accessToken;
        }

        $serviceAccount = json_decode($this->serviceAccountJson, true);
        if (!\is_array($serviceAccount)) {
            throw new \App\Exception\GoogleAuthException('GOOGLE_SEARCH_CONSOLE_KEY JSON invalide');
        }

        $jwt = $this->createJwt($serviceAccount, self::SCOPES);

        $response = $this->httpClient->request('POST', self::TOKEN_URI, [
            'body' => [
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $jwt,
            ],
        ]);

        $data = $response->toArray();
        $this->accessToken = $data['access_token'];
        $this->tokenExpiresAt = time() + ($data['expires_in'] ?? 3600) - 60;

        return $this->accessToken;
    }

    /**
     * @param array<string, mixed> $serviceAccount
     * @param list<string>         $scopes
     */
    private function createJwt(array $serviceAccount, array $scopes): string
    {
        $now = time();

        $header = $this->base64UrlEncode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']));

        $claims = $this->base64UrlEncode(json_encode([
            'iss' => $serviceAccount['client_email'],
            'scope' => implode(' ', $scopes),
            'aud' => self::TOKEN_URI,
            'iat' => $now,
            'exp' => $now + 3600,
        ]));

        $payload = $header.'.'.$claims;

        $privateKey = $serviceAccount['private_key'];
        $signature = '';
        if (!openssl_sign($payload, $signature, $privateKey, \OPENSSL_ALGO_SHA256)) {
            throw new \App\Exception\GoogleAuthException('Erreur signature JWT (openssl_sign)');
        }

        return $payload.'.'.$this->base64UrlEncode($signature);
    }

    private function base64UrlEncode(string $data): string
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
}
