<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Logging;

use App\Framework\Logging\ValueObjects\ExceptionContext;
use PHPUnit\Framework\TestCase;

final class ExceptionContextTest extends TestCase
{
    public function test_creates_from_throwable(): void
    {
        $exception = new \RuntimeException('Test error', 123);
        $context = ExceptionContext::fromThrowable($exception);

        $this->assertEquals(\RuntimeException::class, $context->class);
        $this->assertEquals('RuntimeException', $context->getShortClass());
        $this->assertEquals('Test error', $context->message);
        $this->assertEquals(123, $context->code);
        $this->assertNotEmpty($context->file);
        $this->assertGreaterThan(0, $context->line);
    }

    public function test_captures_stack_trace(): void
    {
        $exception = $this->createException();
        $context = ExceptionContext::fromThrowable($exception);

        $this->assertNotEmpty($context->stackTrace);
        $this->assertContainsOnlyInstancesOf(
            \App\Framework\Logging\ValueObjects\StackFrame::class,
            $context->stackTrace
        );
    }

    public function test_captures_previous_exception(): void
    {
        $previous = new \RuntimeException('Previous error');
        $exception = new \RuntimeException('Current error', 0, $previous);

        $context = ExceptionContext::fromThrowable($exception);

        $this->assertNotNull($context->previous);
        $this->assertEquals('Previous error', $context->previous->message);
        $this->assertEquals(1, $context->getChainLength());
    }

    public function test_captures_exception_chain(): void
    {
        $first = new \RuntimeException('First');
        $second = new \RuntimeException('Second', 0, $first);
        $third = new \RuntimeException('Third', 0, $second);

        $context = ExceptionContext::fromThrowable($third);

        $this->assertEquals(2, $context->getChainLength());

        $chain = $context->getChain();
        $this->assertCount(3, $chain);
        $this->assertEquals('Third', $chain[0]->message);
        $this->assertEquals('Second', $chain[1]->message);
        $this->assertEquals('First', $chain[2]->message);
    }

    public function test_get_top_frames(): void
    {
        $exception = $this->createException();
        $context = ExceptionContext::fromThrowable($exception);

        $topFrames = $context->getTopFrames(3);

        $this->assertCount(min(3, count($context->stackTrace)), $topFrames);
    }

    public function test_to_array(): void
    {
        $exception = new \RuntimeException('Test');
        $context = ExceptionContext::fromThrowable($exception);

        $array = $context->toArray();

        $this->assertArrayHasKey('class', $array);
        $this->assertArrayHasKey('message', $array);
        $this->assertArrayHasKey('file', $array);
        $this->assertArrayHasKey('line', $array);
        $this->assertArrayHasKey('stack_trace', $array);
    }

    public function test_to_compact_array(): void
    {
        $exception = new \RuntimeException('Test');
        $context = ExceptionContext::fromThrowable($exception);

        $array = $context->toCompactArray();

        $this->assertArrayHasKey('class', $array);
        $this->assertArrayHasKey('message', $array);
        $this->assertArrayHasKey('location', $array);
        $this->assertArrayNotHasKey('stack_trace', $array);
    }

    public function test_get_summary(): void
    {
        $exception = new \RuntimeException('Test error');
        $context = ExceptionContext::fromThrowable($exception);

        $summary = $context->getSummary();

        $this->assertStringContainsString('RuntimeException', $summary);
        $this->assertStringContainsString('Test error', $summary);
    }

    public function test_json_serializable(): void
    {
        $exception = new \RuntimeException('Test');
        $context = ExceptionContext::fromThrowable($exception);

        $json = json_encode($context);

        $this->assertNotFalse($json);

        $decoded = json_decode($json, true);
        $this->assertArrayHasKey('class', $decoded);
        $this->assertArrayHasKey('message', $decoded);
    }

    private function createException(): \Exception
    {
        try {
            $this->throwTestException();
        } catch (\Exception $e) {
            return $e;
        }
    }

    private function throwTestException(): void
    {
        throw new \RuntimeException('Test exception');
    }
}
