<?php

declare(strict_types=1);

namespace Tests\Framework\Logging;

use App\Framework\Logging\LogContextManager;
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('LogContextManager', function () {
    beforeEach(function () {
        // Create fresh instance for each test
        $this->contextManager = new LogContextManager();
        TraceContext::clear();
    });

    afterEach(function () {
        // Clean up after each test
        TraceContext::clear();
    });

    it('starts with empty context', function () {
        $context = $this->contextManager->getCurrentContext();

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

    it('can set and get global context', function () {
        $globalContext = LogContext::withData(['app' => 'test'])
            ->addTags('global');

        $this->contextManager->setGlobalContext($globalContext);
        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['app' => 'test']);
        expect($current->tags)->toBe(['global']);
    });

    it('can set and get request context', function () {
        $requestContext = LogContext::withData(['request' => 'test'])
            ->addTags('request');

        $this->contextManager->setRequestContext($requestContext);
        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['request' => 'test']);
        expect($current->tags)->toBe(['request']);
    });

    it('merges global and request contexts', function () {
        $globalContext = LogContext::withData(['app' => 'test'])
            ->addTags('global');

        $requestContext = LogContext::withData(['request' => 'test'])
            ->addTags('request');

        $this->contextManager->setGlobalContext($globalContext);
        $this->contextManager->setRequestContext($requestContext);

        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['app' => 'test', 'request' => 'test']);
        expect($current->tags)->toBe(['global', 'request']);
    });

    it('can add data to global context', function () {
        $this->contextManager->addGlobalData('key1', 'value1');
        $this->contextManager->addGlobalData('key2', 'value2');

        $current = $this->contextManager->getCurrentContext();

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

    it('can add tags to global context', function () {
        $this->contextManager->addGlobalTags('tag1', 'tag2');
        $this->contextManager->addGlobalTags('tag3');

        $current = $this->contextManager->getCurrentContext();

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

    it('can add data to request context', function () {
        $this->contextManager->addRequestData('req1', 'value1');
        $this->contextManager->addRequestData('req2', 'value2');

        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['req1' => 'value1', 'req2' => 'value2']);
    });

    it('can add tags to request context', function () {
        $this->contextManager->addRequestTags('reqtag1', 'reqtag2');
        $this->contextManager->addRequestTags('reqtag3');

        $current = $this->contextManager->getCurrentContext();

        expect($current->tags)->toBe(['reqtag1', 'reqtag2', 'reqtag3']);
    });

    it('includes current trace context automatically', function () {
        $trace = TraceContext::start(new SecureRandomGenerator());

        $current = $this->contextManager->getCurrentContext();

        expect($current->trace)->toBe($trace);
    });

    it('can add user context', function () {
        $userContext = UserContext::authenticated('123', 'john');

        $this->contextManager->withUser($userContext);
        $current = $this->contextManager->getCurrentContext();

        expect($current->user)->toBe($userContext);
    });

    it('can add request context through manager', function () {
        $requestContext = RequestContext::empty();

        $this->contextManager->withRequest($requestContext);
        $current = $this->contextManager->getCurrentContext();

        expect($current->request)->toBe($requestContext);
    });

    it('can add trace context through manager', function () {
        $traceContext = TraceContext::start(new SecureRandomGenerator());

        $this->contextManager->withTrace($traceContext);
        $current = $this->contextManager->getCurrentContext();

        expect($current->trace)->toBe($traceContext);
    });

    it('can clear request context only', function () {
        $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data']));
        $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data']));

        $this->contextManager->clearRequestContext();
        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['global' => 'data']);
    });

    it('can clear global context only', function () {
        $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data']));
        $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data']));

        $this->contextManager->clearGlobalContext();
        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['request' => 'data']);
    });

    it('can clear all contexts', function () {
        $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data']));
        $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data']));

        $this->contextManager->clearContext();
        $current = $this->contextManager->getCurrentContext();

        expect($current->structured)->toBe(['global' => 'data']); // Global remains
    });

    it('can execute callback with temporary context', function () {
        $originalData = ['original' => 'data'];
        $this->contextManager->setRequestContext(LogContext::withData($originalData));

        $temporaryContext = LogContext::withData(['temp' => 'data']);

        $result = $this->contextManager->withTemporaryContext($temporaryContext, function () {
            $current = $this->contextManager->getCurrentContext();

            return $current->structured;
        });

        expect($result)->toBe(['original' => 'data', 'temp' => 'data']);

        // Original context should be restored
        $current = $this->contextManager->getCurrentContext();
        expect($current->structured)->toBe($originalData);
    });

    it('can execute callback with temporary tags', function () {
        $this->contextManager->setRequestContext(LogContext::withTags('original'));

        $result = $this->contextManager->withTemporaryTags(['temp1', 'temp2'], function () {
            $current = $this->contextManager->getCurrentContext();

            return $current->tags;
        });

        expect($result)->toBe(['original', 'temp1', 'temp2']);

        // Original context should be restored
        $current = $this->contextManager->getCurrentContext();
        expect($current->tags)->toBe(['original']);
    });

    it('can execute callback with temporary data', function () {
        $this->contextManager->setRequestContext(LogContext::withData(['original' => 'value']));

        $result = $this->contextManager->withTemporaryData(['temp' => 'value'], function () {
            $current = $this->contextManager->getCurrentContext();

            return $current->structured;
        });

        expect($result)->toBe(['original' => 'value', 'temp' => 'value']);

        // Original context should be restored
        $current = $this->contextManager->getCurrentContext();
        expect($current->structured)->toBe(['original' => 'value']);
    });

    it('restores context even when callback throws exception', function () {
        $originalContext = LogContext::withData(['original' => 'data']);
        $this->contextManager->setRequestContext($originalContext);

        $temporaryContext = LogContext::withData(['temp' => 'data']);

        try {
            $this->contextManager->withTemporaryContext($temporaryContext, function () {
                throw new \RuntimeException('Test exception');
            });
        } catch (\RuntimeException $e) {
            // Expected
        }

        // Original context should be restored
        $current = $this->contextManager->getCurrentContext();
        expect($current->structured)->toBe(['original' => 'data']);
    });

    it('can initialize request context from globals', function () {
        // Mock global variables
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_SERVER['REQUEST_URI'] = '/test';
        $_SERVER['HTTP_HOST'] = 'example.com';
        $_SERVER['SERVER_NAME'] = 'example.com';
        $_SERVER['SERVER_PORT'] = '80';

        // Test direct RequestContext creation first
        $requestContext = \App\Framework\Logging\ValueObjects\RequestContext::fromGlobals();
        expect($requestContext)->not->toBeNull();
        expect($requestContext->getMethod())->toBe('GET');

        $context = $this->contextManager->initializeRequestContext();

        expect($context->request)->not->toBeNull();
        expect($context->request->getMethod())->toBe('GET');
        expect($context->request->getUri())->toBe('/test');
        expect($context->request->getHost())->toBe('example.com');

        // Should be set as current request context
        $current = $this->contextManager->getCurrentContext();
        expect($current->request)->toBe($context->request);

        // Cleanup
        unset($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT']);
    });

    it('provides debug information', function () {
        $this->contextManager->setGlobalContext(LogContext::withData(['global' => 'data']));
        $this->contextManager->setRequestContext(LogContext::withData(['request' => 'data']));

        $debug = $this->contextManager->getDebugInfo();

        expect($debug)->toHaveKeys([
            'has_global_context',
            'has_request_context',
            'global_context',
            'request_context',
            'current_trace',
            'combined_context',
        ]);

        expect($debug['has_global_context'])->toBeTrue();
        expect($debug['has_request_context'])->toBeTrue();
    });

    it('can check for trace context', function () {
        expect($this->contextManager->hasTraceContext())->toBeFalse();

        TraceContext::start(new SecureRandomGenerator());
        expect($this->contextManager->hasTraceContext())->toBeTrue();
    });

    it('can check for user context', function () {
        expect($this->contextManager->hasUserContext())->toBeFalse();

        $this->contextManager->withUser(UserContext::authenticated('123'));
        expect($this->contextManager->hasUserContext())->toBeTrue();
    });

    it('can check for request context', function () {
        expect($this->contextManager->hasRequestContext())->toBeFalse();

        $this->contextManager->withRequest(RequestContext::empty());
        expect($this->contextManager->hasRequestContext())->toBeTrue();
    });
});
