<?php

namespace MintyPHP\Tests\Http;

use MintyPHP\Http\ErrorDataCollector;
use MintyPHP\Http\RequestContext;
use PHPUnit\Framework\TestCase;

class ErrorDataCollectorTest extends TestCase
{
    private array $serverBackup = [];

    protected function setUp(): void
    {
        $this->serverBackup = $_SERVER;
        RequestContext::resetForTests();
    }

    protected function tearDown(): void
    {
        $_SERVER = $this->serverBackup;
        RequestContext::resetForTests();
    }

    public function testCollectReturnsAllFourSections(): void
    {
        $exception = new \RuntimeException('Test exception', 99);
        $result = ErrorDataCollector::collect($exception);

        $this->assertArrayHasKey('exception', $result);
        $this->assertArrayHasKey('request', $result);
        $this->assertArrayHasKey('environment', $result);
        $this->assertArrayHasKey('queries', $result);
    }

    public function testExceptionSectionContainsCorrectDetails(): void
    {
        $exception = new \LogicException('Something wrong', 7);
        $result = ErrorDataCollector::collect($exception);

        $exc = $result['exception'];
        $this->assertSame('LogicException', $exc['class']);
        $this->assertSame('Something wrong', $exc['message']);
        $this->assertSame(7, $exc['code']);
        $this->assertStringContainsString('ErrorDataCollectorTest.php', $exc['file']);
        $this->assertIsInt($exc['line']);
        $this->assertIsArray($exc['trace']);
        $this->assertNotEmpty($exc['trace']);
    }

    public function testChainedExceptionsAreCollected(): void
    {
        $inner = new \InvalidArgumentException('inner cause');
        $outer = new \RuntimeException('outer', 0, $inner);

        $result = ErrorDataCollector::collect($outer);

        $this->assertArrayHasKey('previous', $result['exception']);
        $this->assertSame('InvalidArgumentException', $result['exception']['previous']['class']);
        $this->assertSame('inner cause', $result['exception']['previous']['message']);
    }

    public function testCodeSnippetIsExtractedForKnownFile(): void
    {
        $exception = new \RuntimeException('snippet test');
        $result = ErrorDataCollector::collect($exception);

        $firstFrame = $result['exception']['trace'][0] ?? null;
        $this->assertNotNull($firstFrame);
        $this->assertArrayHasKey('code_snippet', $firstFrame);
        // The snippet should contain lines around the throw site.
        $this->assertNotEmpty($firstFrame['code_snippet']);
    }

    public function testRequestSectionContainsBasicData(): void
    {
        $_SERVER['REQUEST_METHOD'] = 'POST';
        $_SERVER['REQUEST_URI'] = '/admin/users/edit?id=5';
        $_SERVER['REMOTE_ADDR'] = '10.0.0.1';
        $_SERVER['HTTP_USER_AGENT'] = 'TestAgent/1.0';

        $exception = new \RuntimeException('request test');
        $result = ErrorDataCollector::collect($exception);

        $req = $result['request'];
        $this->assertSame('POST', $req['method']);
        $this->assertSame('/admin/users/edit?id=5', $req['url']);
        $this->assertSame('10.0.0.1', $req['ip']);
        $this->assertSame('TestAgent/1.0', $req['user_agent']);
    }

    public function testHeadersAreRedacted(): void
    {
        $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer secret-token';
        $_SERVER['HTTP_COOKIE'] = 'session=abc123';
        $_SERVER['HTTP_X_API_KEY'] = 'my-key';
        $_SERVER['HTTP_ACCEPT'] = 'text/html';

        $exception = new \RuntimeException('header redaction');
        $result = ErrorDataCollector::collect($exception);

        $headers = $result['request']['headers'];
        $this->assertSame('[REDACTED]', $headers['authorization']);
        $this->assertSame('[REDACTED]', $headers['cookie']);
        $this->assertSame('[REDACTED]', $headers['x-api-key']);
        $this->assertSame('text/html', $headers['accept']);
    }

    public function testPostParamsAreRedacted(): void
    {
        $_POST = [
            'username' => 'admin',
            'password' => 'secret123',
            'csrf_token' => 'abc',
            'email' => 'test@example.com',
        ];

        $exception = new \RuntimeException('post redaction');
        $result = ErrorDataCollector::collect($exception);

        $post = $result['request']['post'];
        $this->assertSame('admin', $post['username']);
        $this->assertSame('[REDACTED]', $post['password']);
        $this->assertSame('[REDACTED]', $post['csrf_token']);
        $this->assertSame('test@example.com', $post['email']);

        $_POST = [];
    }

    public function testEnvironmentSectionContainsPhpInfo(): void
    {
        $exception = new \RuntimeException('env test');
        $result = ErrorDataCollector::collect($exception);

        $env = $result['environment'];
        $this->assertSame(PHP_VERSION, $env['php_version']);
        $this->assertSame(PHP_SAPI, $env['php_sapi']);
        $this->assertIsArray($env['extensions']);
        $this->assertNotEmpty($env['extensions']);
        $this->assertIsInt($env['memory_current']);
        $this->assertIsInt($env['memory_peak']);
    }

    public function testArgsAreSummarizedByTypeOnly(): void
    {
        // Trace frames should contain type summaries, not actual argument values.
        $exception = new \RuntimeException('args test');
        $result = ErrorDataCollector::collect($exception);

        foreach ($result['exception']['trace'] as $frame) {
            if (isset($frame['args_summary'])) {
                foreach ($frame['args_summary'] as $summary) {
                    $this->assertIsString($summary);
                    // Should be type descriptions, not raw values.
                    $this->assertMatchesRegularExpression(
                        '/^(null|true|false|int|float|string\(\d+\)|array\(\d+\)|resource|unknown|[A-Z][\w\\\\]*)$/',
                        $summary,
                    );
                }
            }
        }
    }
}
