<?php

declare(strict_types=1);

namespace Tests\Framework\Logging\ValueObjects;

use App\Framework\Logging\ValueObjects\LogContext;
use App\Framework\Logging\ValueObjects\RequestContext;
use App\Framework\Logging\ValueObjects\UserContext;
use App\Framework\Random\SecureRandomGenerator;
use App\Framework\Tracing\TraceContext;

describe('LogContext', function () {
    it('can be created empty', function () {
        $context = LogContext::empty();

        expect($context->structured)->toBe([]);
        expect($context->tags)->toBe([]);
        expect($context->trace)->toBeNull();
        expect($context->user)->toBeNull();
        expect($context->request)->toBeNull();
        expect($context->metadata)->toBe([]);
    });

    it('can be created with structured data', function () {
        $data = ['user_id' => 123, 'action' => 'login'];
        $context = LogContext::withData($data);

        expect($context->structured)->toBe($data);
        expect($context->hasStructuredData())->toBeTrue();
    });

    it('can be created with tags', function () {
        $context = LogContext::withTags('auth', 'user-action');

        expect($context->tags)->toBe(['auth', 'user-action']);
        expect($context->hasTags())->toBeTrue();
        expect($context->hasTag('auth'))->toBeTrue();
        expect($context->hasTag('missing'))->toBeFalse();
    });

    it('can add structured data immutably', function () {
        $original = LogContext::withData(['key1' => 'value1']);
        $modified = $original->addData('key2', 'value2');

        expect($original->structured)->toBe(['key1' => 'value1']);
        expect($modified->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']);
    });

    it('can merge structured data immutably', function () {
        $original = LogContext::withData(['key1' => 'value1']);
        $modified = $original->mergeData(['key2' => 'value2', 'key3' => 'value3']);

        expect($original->structured)->toBe(['key1' => 'value1']);
        expect($modified->structured)->toBe([
            'key1' => 'value1',
            'key2' => 'value2',
            'key3' => 'value3',
        ]);
    });

    it('can add tags immutably', function () {
        $original = LogContext::withTags('tag1');
        $modified = $original->addTags('tag2', 'tag3');

        expect($original->tags)->toBe(['tag1']);
        expect($modified->tags)->toBe(['tag1', 'tag2', 'tag3']);
    });

    it('removes duplicate tags when adding', function () {
        $original = LogContext::withTags('tag1', 'tag2');
        $modified = $original->addTags('tag2', 'tag3');

        expect($modified->tags)->toBe(['tag1', 'tag2', 'tag3']);
    });

    it('can add user context immutably', function () {
        $userContext = UserContext::authenticated('123', 'john');
        $original = LogContext::empty();
        $modified = $original->withUser($userContext);

        expect($original->user)->toBeNull();
        expect($modified->user)->toBe($userContext);
    });

    it('can add request context immutably', function () {
        $requestContext = RequestContext::empty();
        $original = LogContext::empty();
        $modified = $original->withRequest($requestContext);

        expect($original->request)->toBeNull();
        expect($modified->request)->toBe($requestContext);
    });

    it('can add trace context immutably', function () {
        $traceContext = TraceContext::start(new SecureRandomGenerator());
        $original = LogContext::empty();
        $modified = $original->withTrace($traceContext);

        expect($original->trace)->toBeNull();
        expect($modified->trace)->toBe($traceContext);
    });

    it('can add metadata immutably', function () {
        $original = LogContext::empty();
        $modified = $original->addMetadata('source', 'test');

        expect($original->metadata)->toBe([]);
        expect($modified->metadata)->toBe(['source' => 'test']);
    });

    it('can merge two contexts', function () {
        $context1 = LogContext::withData(['key1' => 'value1'])
            ->addTags('tag1')
            ->addMetadata('meta1', 'value1');

        $context2 = LogContext::withData(['key2' => 'value2'])
            ->addTags('tag2')
            ->addMetadata('meta2', 'value2');

        $merged = $context1->merge($context2);

        expect($merged->structured)->toBe(['key1' => 'value1', 'key2' => 'value2']);
        expect($merged->tags)->toBe(['tag1', 'tag2']);
        expect($merged->metadata)->toBe(['meta1' => 'value1', 'meta2' => 'value2']);
    });

    it('prioritizes second context in merge for trace, user, request', function () {
        $user1 = UserContext::authenticated('123');
        $user2 = UserContext::authenticated('456');
        $request1 = RequestContext::empty();
        $request2 = RequestContext::empty();
        $trace1 = TraceContext::start(new SecureRandomGenerator());
        $trace2 = TraceContext::start(new SecureRandomGenerator());

        $context1 = LogContext::empty()
            ->withUser($user1)
            ->withRequest($request1)
            ->withTrace($trace1);

        $context2 = LogContext::empty()
            ->withUser($user2)
            ->withRequest($request2)
            ->withTrace($trace2);

        $merged = $context1->merge($context2);

        expect($merged->user)->toBe($user2);
        expect($merged->request)->toBe($request2);
        expect($merged->trace)->toBe($trace2);
    });

    it('can convert to array', function () {
        $userContext = UserContext::authenticated('123', 'john');
        $requestContext = RequestContext::empty();
        $traceContext = TraceContext::start(new SecureRandomGenerator());

        $context = LogContext::withData(['key' => 'value'])
            ->addTags('tag1', 'tag2')
            ->withUser($userContext)
            ->withRequest($requestContext)
            ->withTrace($traceContext)
            ->addMetadata('source', 'test');

        $array = $context->toArray();

        expect($array)->toHaveKeys([
            'structured',
            'tags',
            'trace',
            'user',
            'request',
            'metadata',
        ]);

        expect($array['structured'])->toBe(['key' => 'value']);
        expect($array['tags'])->toBe(['tag1', 'tag2']);
        expect($array['metadata'])->toBe(['source' => 'test']);
    });

    it('only includes non-empty sections in toArray', function () {
        $context = LogContext::withData(['key' => 'value']);
        $array = $context->toArray();

        expect($array)->toHaveKey('structured');
        expect($array)->not->toHaveKey('tags');
        expect($array)->not->toHaveKey('trace');
        expect($array)->not->toHaveKey('user');
        expect($array)->not->toHaveKey('request');
        expect($array)->not->toHaveKey('metadata');
    });

    it('can create context with current trace', function () {
        // Clear any existing trace
        TraceContext::clear();

        // Start a new trace
        $trace = TraceContext::start(new SecureRandomGenerator());

        $context = LogContext::withCurrentTrace();

        expect($context->trace)->toBe($trace);

        // Cleanup
        TraceContext::clear();
    });

    it('creates context with null trace when no current trace', function () {
        TraceContext::clear();

        $context = LogContext::withCurrentTrace();

        expect($context->trace)->toBeNull();
    });
});
