<?php

namespace MintyPHP\Tests\Http;

use MintyPHP\Http\ErrorRenderer;
use MintyPHP\I18n;
use PHPUnit\Framework\TestCase;

class ErrorRendererTest extends TestCase
{
    private string $localeBackup = '';
    private string $defaultLocaleBackup = '';

    protected function setUp(): void
    {
        $this->localeBackup = (string) (I18n::$locale ?? '');
        $this->defaultLocaleBackup = (string) (I18n::$defaultLocale ?? 'en');
        I18n::$defaultLocale = 'en';
        I18n::$locale = 'en';
    }

    protected function tearDown(): void
    {
        I18n::$locale = $this->localeBackup;
        I18n::$defaultLocale = $this->defaultLocaleBackup;
    }

    public function testRenderDevReturnsCompleteHtmlDocument(): void
    {
        $data = self::sampleData();
        $html = ErrorRenderer::renderDev($data);

        $this->assertStringContainsString('<!DOCTYPE html>', $html);
        $this->assertStringContainsString('</html>', $html);
        $this->assertStringContainsString('<style>', $html);
        $this->assertStringContainsString('<script>', $html);
        $this->assertStringContainsString('<html lang="en">', $html);
    }

    public function testRenderDevContainsExceptionDetails(): void
    {
        $data = self::sampleData();
        $html = ErrorRenderer::renderDev($data);

        $this->assertStringContainsString('RuntimeException', $html);
        $this->assertStringContainsString('Test error message', $html);
        $this->assertStringContainsString('test-request-id-123', $html);
    }

    public function testRenderDevContainsAllFourTabs(): void
    {
        $data = self::sampleData();
        $html = ErrorRenderer::renderDev($data);

        $this->assertStringContainsString('panel-exception', $html);
        $this->assertStringContainsString('panel-request', $html);
        $this->assertStringContainsString('panel-environment', $html);
        $this->assertStringContainsString('panel-queries', $html);
        $this->assertStringContainsString('role="tablist"', $html);
        $this->assertStringContainsString('aria-controls="panel-exception"', $html);
        $this->assertStringContainsString('role="tabpanel"', $html);
    }

    public function testRenderDevUsesAccessibleToggleButtons(): void
    {
        $data = self::sampleData();
        $html = ErrorRenderer::renderDev($data);

        $this->assertStringContainsString('app-error-debug-frame__toggle', $html);
        $this->assertStringContainsString('app-error-debug-query__toggle', $html);
        $this->assertStringContainsString('aria-expanded="true"', $html);
        $this->assertStringContainsString('aria-controls="frame-code-0"', $html);
        $this->assertStringContainsString('aria-label="Copy request ID"', $html);
    }

    public function testRenderDevEscapesHtmlInExceptionMessage(): void
    {
        $data = self::sampleData();
        $data['exception']['message'] = '<script>alert("xss")</script>';
        $html = ErrorRenderer::renderDev($data);

        $this->assertStringNotContainsString('<script>alert("xss")</script>', $html);
        $this->assertStringContainsString('&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;', $html);
    }

    public function testRenderDevShowsRedactedValues(): void
    {
        $data = self::sampleData();
        $data['request']['headers']['authorization'] = '[REDACTED]';
        $html = ErrorRenderer::renderDev($data);

        $this->assertStringContainsString('[REDACTED]', $html);
    }

    public function testRenderProdShowsRequestIdOnly(): void
    {
        $html = ErrorRenderer::renderProd('abc-def-123');

        $this->assertStringContainsString('<!DOCTYPE html>', $html);
        $this->assertStringContainsString('<html lang="en">', $html);
        $this->assertStringContainsString('abc-def-123', $html);
        $this->assertStringContainsString('An unexpected error occurred', $html);
        $this->assertStringContainsString('Back to start', $html);
        $this->assertStringContainsString('data-copy="abc-def-123"', $html);
        $this->assertStringContainsString('aria-label="Copy request ID"', $html);
    }

    public function testRenderProdDoesNotContainDebugInfo(): void
    {
        $html = ErrorRenderer::renderProd('request-id');

        // Prod page must not render actual debug HTML elements (panels, stack frames).
        // Note: CSS class names may appear in the inlined stylesheet — we check for HTML attributes only.
        $this->assertStringNotContainsString('id="panel-exception"', $html);
        $this->assertStringNotContainsString('id="panel-request"', $html);
        $this->assertStringNotContainsString('data-panel=', $html);
    }

    public function testRenderProdEscapesRequestId(): void
    {
        $html = ErrorRenderer::renderProd('<script>alert(1)</script>');

        $this->assertStringNotContainsString('<script>alert(1)</script>', $html);
        $this->assertStringContainsString('&lt;script&gt;', $html);
    }

    public function testEscMethodEscapesSpecialChars(): void
    {
        $this->assertSame('&lt;b&gt;test&lt;/b&gt;', ErrorRenderer::esc('<b>test</b>'));
        $this->assertSame('a &amp; b', ErrorRenderer::esc('a & b'));
        $this->assertSame('&quot;quoted&quot;', ErrorRenderer::esc('"quoted"'));
        $this->assertSame('it&#039;s', ErrorRenderer::esc("it's"));
    }

    /**
     * @return array{exception: array<string, mixed>, request: array<string, mixed>, environment: array<string, mixed>, queries: array<string, mixed>}
     */
    private static function sampleData(): array
    {
        return [
            'exception' => [
                'class' => 'RuntimeException',
                'message' => 'Test error message',
                'code' => 0,
                'file' => '/app/web/index.php',
                'line' => 45,
                'trace' => [
                    [
                        'file' => '/app/web/index.php',
                        'line' => 45,
                        'function' => null,
                        'class' => null,
                        'code_snippet' => [
                            44 => '    $expected = "abc";',
                            45 => '    throw new RuntimeException("Test");',
                            46 => '    // unreachable',
                        ],
                    ],
                ],
            ],
            'request' => [
                'request_id' => 'test-request-id-123',
                'channel' => 'web',
                'method' => 'GET',
                'url' => '/admin/dashboard',
                'ip' => '127.0.0.1',
                'user_agent' => 'TestAgent/1.0',
                'headers' => ['accept' => 'text/html'],
                'get' => [],
                'post' => [],
                'session_keys' => [],
                'user_id' => null,
                'tenant_id' => null,
            ],
            'environment' => [
                'php_version' => '8.5.0',
                'php_sapi' => 'cli',
                'extensions' => ['Core', 'json', 'mbstring'],
                'memory_current' => 8 * 1024 * 1024,
                'memory_peak' => 12 * 1024 * 1024,
                'env' => ['APP_DEBUG' => 'true', 'APP_ENV' => 'dev'],
                'modules' => [],
            ],
            'queries' => [
                'available' => true,
                'queries' => [],
                'start' => microtime(true),
                'count' => 0,
            ],
        ];
    }
}
