<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Monitoring;

use App\Shared\Infrastructure\Tenant\TenantContext;

use function in_array;
use function is_string;

use Prometheus\CollectorRegistry;
use Prometheus\Counter;
use Prometheus\Histogram;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Collects HTTP request metrics for Prometheus.
 *
 * Tracks request latency (P50, P95, P99), error rates, and requests per second.
 * Metrics are labeled by tenant for multi-tenant analysis.
 *
 * @see Story 1.8 - T3.4: Custom metrics (requests_total, request_duration_seconds)
 */
final class MetricsCollector
{
    private const string NAMESPACE = 'classeo';

    private Counter $requestsTotal;
    private Histogram $requestDuration;
    private Counter $loginFailures;
    private ?float $requestStartTime = null;

    public function __construct(
        private readonly CollectorRegistry $registry,
        private readonly TenantContext $tenantContext,
    ) {
        $this->initializeMetrics();
    }

    private function initializeMetrics(): void
    {
        $this->requestsTotal = $this->registry->getOrRegisterCounter(
            self::NAMESPACE,
            'http_requests_total',
            'Total number of HTTP requests',
            ['method', 'route', 'status', 'tenant_id'],
        );

        $this->requestDuration = $this->registry->getOrRegisterHistogram(
            self::NAMESPACE,
            'http_request_duration_seconds',
            'HTTP request duration in seconds',
            ['method', 'route', 'tenant_id'],
            // Buckets optimized for SLA monitoring (P95 < 200ms, P99 < 500ms)
            [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.75, 1.0, 2.5, 5.0, 10.0],
        );

        $this->loginFailures = $this->registry->getOrRegisterCounter(
            self::NAMESPACE,
            'login_failures_total',
            'Total number of failed login attempts',
            ['tenant_id', 'reason'],
        );
    }

    #[AsEventListener(event: KernelEvents::REQUEST, priority: 1024)]
    public function onKernelRequest(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return;
        }

        $this->requestStartTime = microtime(true);
    }

    #[AsEventListener(event: KernelEvents::TERMINATE, priority: 1024)]
    public function onKernelTerminate(TerminateEvent $event): void
    {
        if ($this->requestStartTime === null) {
            return;
        }

        $request = $event->getRequest();
        $response = $event->getResponse();

        // Skip metrics endpoints to avoid self-referential noise
        $routeValue = $request->attributes->get('_route', 'unknown');
        $route = is_string($routeValue) ? $routeValue : 'unknown';
        if (in_array($route, ['prometheus_metrics', 'health_check'], true)) {
            $this->requestStartTime = null;

            return;
        }

        $method = $request->getMethod();
        $status = (string) $response->getStatusCode();
        $tenantId = $this->tenantContext->hasTenant()
            ? $this->tenantContext->getCurrentTenantConfig()->subdomain
            : 'none';

        $duration = microtime(true) - $this->requestStartTime;

        // Record request count
        $this->requestsTotal->inc([$method, $route, $status, $tenantId]);

        // Record request duration
        $this->requestDuration->observe($duration, [$method, $route, $tenantId]);

        $this->requestStartTime = null;
    }

    /**
     * Record a failed login attempt.
     *
     * Called by the authentication system to track brute force attempts.
     */
    public function recordLoginFailure(string $reason = 'invalid_credentials'): void
    {
        $tenantId = $this->tenantContext->hasTenant()
            ? $this->tenantContext->getCurrentTenantConfig()->subdomain
            : 'none';

        $this->loginFailures->inc([$tenantId, $reason]);
    }
}
