<?php

declare(strict_types=1);

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

describe('LogContext', function () {
    describe('empty()', function () {
        it('creates empty LogContext', 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([]);
        });
    });

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

            expect($context->structured)->toBe($data);
            expect($context->tags)->toBe([]);
        });
    });

    describe('withTags()', function () {
        it('creates LogContext with tags', function () {
            $context = LogContext::withTags('security', 'authentication');

            expect($context->tags)->toBe(['security', 'authentication']);
            expect($context->structured)->toBe([]);
        });

        it('accepts variadic tags', function () {
            $context = LogContext::withTags('tag1', 'tag2', 'tag3');

            expect($context->tags)->toHaveCount(3);
            expect($context->tags)->toContain('tag1', 'tag2', 'tag3');
        });
    });

    describe('addData()', function () {
        it('adds single data entry', function () {
            $context = LogContext::empty()->addData('key', 'value');

            expect($context->structured)->toBe(['key' => 'value']);
        });

        it('preserves existing data when adding new', function () {
            $context = LogContext::withData(['existing' => 'data'])
                ->addData('new', 'value');

            expect($context->structured)->toBe([
                'existing' => 'data',
                'new' => 'value',
            ]);
        });

        it('returns new instance (immutability)', function () {
            $original = LogContext::withData(['original' => 'data']);
            $modified = $original->addData('new', 'value');

            expect($original->structured)->toBe(['original' => 'data']);
            expect($modified->structured)->toBe([
                'original' => 'data',
                'new' => 'value',
            ]);
            expect($original)->not->toBe($modified);
        });
    });

    describe('mergeData()', function () {
        it('merges multiple data entries', function () {
            $context = LogContext::withData(['existing' => 'data'])
                ->mergeData(['new1' => 'value1', 'new2' => 'value2']);

            expect($context->structured)->toBe([
                'existing' => 'data',
                'new1' => 'value1',
                'new2' => 'value2',
            ]);
        });

        it('overwrites existing keys', function () {
            $context = LogContext::withData(['key' => 'old'])
                ->mergeData(['key' => 'new']);

            expect($context->structured['key'])->toBe('new');
        });
    });

    describe('addTags()', function () {
        it('adds tags to existing tags', function () {
            $context = LogContext::withTags('tag1')
                ->addTags('tag2', 'tag3');

            expect($context->tags)->toHaveCount(3);
            expect($context->tags)->toContain('tag1', 'tag2', 'tag3');
        });

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

            expect($context->tags)->toHaveCount(3);
            expect($context->tags)->toContain('tag1', 'tag2', 'tag3');
        });

        it('returns new instance (immutability)', function () {
            $original = LogContext::withTags('tag1');
            $modified = $original->addTags('tag2');

            expect($original->tags)->toBe(['tag1']);
            expect($modified->tags)->toHaveCount(2);
            expect($original)->not->toBe($modified);
        });
    });

    describe('withTrace()', function () {
        it('sets trace context', function () {
            $random = new SecureRandomGenerator();
            $trace = TraceContext::start($random, 'trace-id-123');
            $context = LogContext::empty()->withTrace($trace);

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

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

    describe('withUser()', function () {
        it('sets user context', function () {
            $user = new UserContext('user-123', 'john@example.com');
            $context = LogContext::empty()->withUser($user);

            expect($context->user)->toBe($user);
        });
    });

    describe('withRequest()', function () {
        it('sets request context', function () {
            $request = RequestContext::empty();
            $context = LogContext::empty()->withRequest($request);

            expect($context->request)->toBe($request);
        });
    });

    describe('addMetadata()', function () {
        it('adds metadata entry', function () {
            $context = LogContext::empty()->addMetadata('key', 'value');

            expect($context->metadata)->toBe(['key' => 'value']);
        });

        it('preserves existing metadata', function () {
            $context = LogContext::empty()
                ->addMetadata('key1', 'value1')
                ->addMetadata('key2', 'value2');

            expect($context->metadata)->toBe([
                'key1' => 'value1',
                'key2' => 'value2',
            ]);
        });
    });

    describe('merge()', function () {
        it('merges two LogContexts', function () {
            $context1 = LogContext::withData(['key1' => 'value1'])
                ->addTags('tag1');

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

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

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

        it('overwrites structured data on merge', function () {
            $context1 = LogContext::withData(['key' => 'old']);
            $context2 = LogContext::withData(['key' => 'new']);

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

            expect($merged->structured['key'])->toBe('new');
        });

        it('removes duplicate tags on merge', function () {
            $context1 = LogContext::withTags('tag1', 'tag2');
            $context2 = LogContext::withTags('tag2', 'tag3');

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

            expect($merged->tags)->toHaveCount(3);
            expect($merged->tags)->toContain('tag1', 'tag2', 'tag3');
        });

        it('prefers other context for trace/user/request', function () {
            $random = new SecureRandomGenerator();
            $trace1 = TraceContext::start($random, 'trace-1');
            TraceContext::clear();
            $trace2 = TraceContext::start($random, 'trace-2');

            $user1 = new UserContext('user-1', 'user1@example.com');
            $user2 = new UserContext('user-2', 'user2@example.com');

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

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

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

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

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

        it('keeps original trace/user/request when other is null', function () {
            $random = new SecureRandomGenerator();
            $trace = TraceContext::start($random, 'trace-1');
            $user = new UserContext('user-1', 'user1@example.com');

            $context1 = LogContext::empty()
                ->withTrace($trace)
                ->withUser($user);

            $context2 = LogContext::withData(['key' => 'value']);

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

            expect($merged->trace)->toBe($trace);
            expect($merged->user)->toBe($user);

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

    describe('hasStructuredData()', function () {
        it('returns true when structured data exists', function () {
            $context = LogContext::withData(['key' => 'value']);

            expect($context->hasStructuredData())->toBeTrue();
        });

        it('returns false when no structured data', function () {
            $context = LogContext::empty();

            expect($context->hasStructuredData())->toBeFalse();
        });
    });

    describe('hasTags()', function () {
        it('returns true when tags exist', function () {
            $context = LogContext::withTags('tag1');

            expect($context->hasTags())->toBeTrue();
        });

        it('returns false when no tags', function () {
            $context = LogContext::empty();

            expect($context->hasTags())->toBeFalse();
        });
    });

    describe('hasTag()', function () {
        it('returns true when specific tag exists', function () {
            $context = LogContext::withTags('security', 'auth');

            expect($context->hasTag('security'))->toBeTrue();
            expect($context->hasTag('auth'))->toBeTrue();
        });

        it('returns false when tag does not exist', function () {
            $context = LogContext::withTags('security');

            expect($context->hasTag('performance'))->toBeFalse();
        });

        it('uses strict comparison', function () {
            $context = LogContext::withTags('123');

            expect($context->hasTag('123'))->toBeTrue();
            // hasTag expects string, so we don't test with int
        });
    });

    describe('toArray()', function () {
        it('returns empty array for empty context', function () {
            $context = LogContext::empty();

            expect($context->toArray())->toBe([]);
        });

        it('includes structured data when present', function () {
            $context = LogContext::withData(['key' => 'value']);

            $array = $context->toArray();

            expect($array)->toHaveKey('structured');
            expect($array['structured'])->toBe(['key' => 'value']);
        });

        it('includes tags when present', function () {
            $context = LogContext::withTags('tag1', 'tag2');

            $array = $context->toArray();

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

        it('includes trace when present', function () {
            $random = new SecureRandomGenerator();
            $trace = TraceContext::start($random, 'trace-123');
            $context = LogContext::empty()->withTrace($trace);

            $array = $context->toArray();

            expect($array)->toHaveKey('trace');
            expect($array['trace'])->toHaveKey('trace_id');
            expect($array['trace']['trace_id'])->toBe('trace-123');

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

        it('includes user when present', function () {
            $user = new UserContext('user-123', 'john@example.com');
            $context = LogContext::empty()->withUser($user);

            $array = $context->toArray();

            expect($array)->toHaveKey('user');
            expect($array['user'])->toBeArray();
        });

        it('includes request when present', function () {
            $request = RequestContext::empty();
            $context = LogContext::empty()->withRequest($request);

            $array = $context->toArray();

            expect($array)->toHaveKey('request');
            expect($array['request'])->toBeArray();
        });

        it('includes metadata when present', function () {
            $context = LogContext::empty()->addMetadata('key', 'value');

            $array = $context->toArray();

            expect($array)->toHaveKey('metadata');
            expect($array['metadata'])->toBe(['key' => 'value']);
        });

        it('includes all sections when all are present', function () {
            $random = new SecureRandomGenerator();
            $trace = TraceContext::start($random, 'trace-123');
            $user = new UserContext('user-123', 'john@example.com');
            $request = RequestContext::empty();

            $context = LogContext::withData(['key' => 'value'])
                ->addTags('security')
                ->withTrace($trace)
                ->withUser($user)
                ->withRequest($request)
                ->addMetadata('meta', 'data');

            $array = $context->toArray();

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

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

    describe('immutability', function () {
        it('is immutable through fluent API', function () {
            $original = LogContext::empty();

            $modified = $original
                ->addData('key', 'value')
                ->addTags('tag')
                ->addMetadata('meta', 'value');

            // Original remains unchanged
            expect($original->structured)->toBe([]);
            expect($original->tags)->toBe([]);
            expect($original->metadata)->toBe([]);

            // Modified has all changes
            expect($modified->structured)->toBe(['key' => 'value']);
            expect($modified->tags)->toBe(['tag']);
            expect($modified->metadata)->toBe(['meta' => 'value']);
        });
    });
});
