<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Audit trail for every webhook request that reaches the app (Symfony's
 * `_webhook_controller`, i.e. `/webhook/{type}`): one line per request with its
 * outcome — accepted, or rejected with the status and reason.
 *
 * Without this, a rejected webhook is silent: the controller returns the reject
 * response but logs nothing, and the prod `fingers_crossed` handler drops
 * anything below `error`. Routed to the always-on `ferienpass_webhook` channel
 * ({@see \Ferienpass\CoreBundle\DependencyInjection\FerienpassCoreExtension::prepend})
 * so these survive.
 *
 * Note: this only sees requests that make it past any reverse-proxy auth (e.g. a
 * Traefik basic-auth 401 never reaches PHP, so it can't be logged here — check
 * the proxy / provider webhook logs for those).
 */
#[AsEventListener(event: KernelEvents::RESPONSE)]
final readonly class WebhookRequestLogger
{
    public function __construct(private LoggerInterface $webhookLogger)
    {
    }

    public function __invoke(ResponseEvent $event): void
    {
        $request = $event->getRequest();
        if (!$event->isMainRequest() || '_webhook_controller' !== $request->attributes->get('_route')) {
            return;
        }

        $status = $event->getResponse()->getStatusCode();
        $context = [
            'type' => (string) $request->attributes->get('type', '?'),
            'status' => $status,
            'ip' => $request->getClientIp(),
        ];

        if ($status >= 400) {
            $context['reason'] = $this->reason((string) $event->getResponse()->getContent());
            $this->webhookLogger->warning('Webhook rejected: {type} → {status}', $context);

            return;
        }

        $this->webhookLogger->info('Webhook accepted: {type} → {status}', $context);
    }

    /**
     * The reject reason as a short clean string. Symfony's error page leads with
     * "<!-- message (status) -->", so prefer that; otherwise strip any markup
     * from a plain-text rejection body.
     */
    private function reason(string $body): string
    {
        if (1 === preg_match('/<!--\s*(.+?)\s*-->/s', $body, $m)) {
            $body = $m[1];
        }

        return mb_substr(trim((string) preg_replace('/\s+/', ' ', strip_tags($body))), 0, 200);
    }
}
