<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Infrastructure\Monitoring;

use App\Shared\Infrastructure\Monitoring\HealthMetricsCollectorInterface;
use App\Shared\Infrastructure\Monitoring\MessengerMetricsCollectorInterface;
use App\Shared\Infrastructure\Monitoring\MetricsController;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Prometheus\CollectorRegistry;
use Prometheus\MetricFamilySamples;
use Prometheus\RenderTextFormat;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
 * Stub for HealthMetricsCollectorInterface that doesn't make real connections.
 */
final class HealthMetricsCollectorStub implements HealthMetricsCollectorInterface
{
    private bool $collected = false;

    public function collect(): void
    {
        $this->collected = true;
    }

    public function wasCollectCalled(): bool
    {
        return $this->collected;
    }
}

/**
 * @see Story 1.8 - T3.3: Expose /metrics endpoint in backend
 */
#[CoversClass(MetricsController::class)]
final class MetricsControllerTest extends TestCase
{
    private function createController(
        ?CollectorRegistry $registry = null,
        string $appEnv = 'dev',
    ): MetricsController {
        $registry ??= $this->createMock(CollectorRegistry::class);
        $registry->method('getMetricFamilySamples')->willReturn([]);

        $healthMetrics = new HealthMetricsCollectorStub();
        $messengerMetrics = $this->createMock(MessengerMetricsCollectorInterface::class);

        return new MetricsController($registry, $healthMetrics, $messengerMetrics, $appEnv);
    }

    #[Test]
    public function itReturnsMetricsWithCorrectContentType(): void
    {
        $controller = $this->createController();
        $request = Request::create('/metrics');

        $response = $controller($request);

        self::assertSame(Response::HTTP_OK, $response->getStatusCode());
        self::assertSame(RenderTextFormat::MIME_TYPE, $response->headers->get('Content-Type'));
    }

    #[Test]
    public function itRendersMetricsFromRegistry(): void
    {
        $sample = new MetricFamilySamples([
            'name' => 'test_counter',
            'type' => 'counter',
            'help' => 'A test counter',
            'labelNames' => [],
            'samples' => [
                [
                    'name' => 'test_counter',
                    'labelNames' => [],
                    'labelValues' => [],
                    'value' => 42,
                ],
            ],
        ]);

        $registry = $this->createMock(CollectorRegistry::class);
        $registry->method('getMetricFamilySamples')->willReturn([$sample]);

        $healthMetrics = new HealthMetricsCollectorStub();
        $messengerMetrics = $this->createMock(MessengerMetricsCollectorInterface::class);
        $controller = new MetricsController($registry, $healthMetrics, $messengerMetrics);
        $request = Request::create('/metrics');

        $response = $controller($request);

        $content = $response->getContent();
        self::assertStringContainsString('test_counter', $content);
        self::assertStringContainsString('42', $content);
    }

    #[Test]
    public function itReturnsEmptyResponseWhenNoMetrics(): void
    {
        $controller = $this->createController();
        $request = Request::create('/metrics');

        $response = $controller($request);

        self::assertSame(Response::HTTP_OK, $response->getStatusCode());
        self::assertIsString($response->getContent());
    }

    #[Test]
    public function itAllowsInternalIpInProduction(): void
    {
        $controller = $this->createController(appEnv: 'prod');
        $request = Request::create('/metrics', server: ['REMOTE_ADDR' => '172.18.0.5']);

        $response = $controller($request);

        self::assertSame(Response::HTTP_OK, $response->getStatusCode());
    }

    #[Test]
    public function itBlocksExternalIpInProduction(): void
    {
        $registry = $this->createMock(CollectorRegistry::class);
        $healthMetrics = new HealthMetricsCollectorStub();
        $messengerMetrics = $this->createMock(MessengerMetricsCollectorInterface::class);
        $controller = new MetricsController($registry, $healthMetrics, $messengerMetrics, 'prod');
        $request = Request::create('/metrics', server: ['REMOTE_ADDR' => '8.8.8.8']);

        $this->expectException(AccessDeniedHttpException::class);
        $this->expectExceptionMessage('Metrics endpoint is restricted to internal networks.');

        $controller($request);
    }

    #[Test]
    public function itAllowsAnyIpInDev(): void
    {
        $controller = $this->createController(appEnv: 'dev');
        $request = Request::create('/metrics', server: ['REMOTE_ADDR' => '8.8.8.8']);

        $response = $controller($request);

        self::assertSame(Response::HTTP_OK, $response->getStatusCode());
    }

    #[Test]
    public function itAllowsLocalhostInProduction(): void
    {
        $controller = $this->createController(appEnv: 'prod');
        $request = Request::create('/metrics', server: ['REMOTE_ADDR' => '127.0.0.1']);

        $response = $controller($request);

        self::assertSame(Response::HTTP_OK, $response->getStatusCode());
    }

    #[Test]
    public function itCollectsHealthMetricsBeforeRendering(): void
    {
        $registry = $this->createMock(CollectorRegistry::class);
        $registry->method('getMetricFamilySamples')->willReturn([]);

        $healthMetrics = new HealthMetricsCollectorStub();
        $messengerMetrics = $this->createMock(MessengerMetricsCollectorInterface::class);
        $controller = new MetricsController($registry, $healthMetrics, $messengerMetrics);
        $request = Request::create('/metrics');

        $controller($request);

        self::assertTrue($healthMetrics->wasCollectCalled());
    }
}
