<?php

declare(strict_types=1);

use App\Framework\Logging\Formatter\StructuredFormatter;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;

describe('StructuredFormatter', function () {
    beforeEach(function () {
        $this->timestamp = new DateTimeImmutable('2024-01-15 10:30:45', new DateTimeZone('Europe/Berlin'));
    });

    describe('constructor', function () {
        it('accepts default format', function () {
            $formatter = new StructuredFormatter();

            expect($formatter instanceof StructuredFormatter)->toBeTrue();
        });

        it('accepts logfmt format', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            expect($formatter instanceof StructuredFormatter)->toBeTrue();
        });

        it('accepts kv format', function () {
            $formatter = new StructuredFormatter(format: 'kv');

            expect($formatter instanceof StructuredFormatter)->toBeTrue();
        });

        it('accepts array format', function () {
            $formatter = new StructuredFormatter(format: 'array');

            expect($formatter instanceof StructuredFormatter)->toBeTrue();
        });
    });

    describe('logfmt format', function () {
        it('formats as logfmt string', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::withData(['user_id' => 123]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result)->toBeString();
            // Implementation uses abbreviated keys: ts, level, msg
            expect(str_contains($result, 'level=INFO'))->toBeTrue();
            expect(str_contains($result, 'msg="Test message"'))->toBeTrue();
            expect(str_contains($result, 'user_id=123'))->toBeTrue();
        });

        it('quotes values with spaces', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            $record = new LogRecord(
                message: 'Multi word message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(str_contains($result, 'msg="Multi word message"'))->toBeTrue();
        });

        it('quotes values with equals sign', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData(['equation' => '2+2=4']),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(str_contains($result, 'equation="2+2=4"'))->toBeTrue();
        });

        it('escapes quotes in values', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData(['quote' => 'He said "hello"']),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(str_contains($result, 'quote="He said \\"hello\\""'))->toBeTrue();
        });

        it('includes timestamp', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            // Implementation uses 'ts' key
            expect(str_contains($result, 'ts='))->toBeTrue();
            expect(str_contains($result, '2024-01-15'))->toBeTrue();
        });

        it('includes channel when provided', function () {
            $formatter = new StructuredFormatter(format: 'logfmt');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp,
                channel: 'security'
            );

            $result = $formatter($record);

            expect(str_contains($result, 'channel=security'))->toBeTrue();
        });
    });

    describe('kv format', function () {
        it('formats as key-value string', function () {
            $formatter = new StructuredFormatter(format: 'kv');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::withData(['user_id' => 123]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result)->toBeString();
            expect(str_contains($result, 'level: INFO'))->toBeTrue();
            expect(str_contains($result, 'msg: Test message'))->toBeTrue();
            expect(str_contains($result, 'user_id: 123'))->toBeTrue();
        });

        it('uses colon separator', function () {
            $formatter = new StructuredFormatter(format: 'kv');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData(['key' => 'value']),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(str_contains($result, 'key: value'))->toBeTrue();
            expect(str_contains($result, '='))->toBeFalse();
        });
    });

    describe('array format', function () {
        it('returns array', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result)->toBeArray();
        });

        it('includes all standard fields', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp,
                channel: 'app'
            );

            $result = $formatter($record);

            // Implementation uses abbreviated keys
            expect(isset($result['ts']))->toBeTrue();
            expect(isset($result['level']))->toBeTrue();
            expect(isset($result['msg']))->toBeTrue();
            expect(isset($result['channel']))->toBeTrue();
        });

        it('includes context data', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test message',
                context: LogContext::withData([
                    'user_id' => 123,
                    'action' => 'login'
                ]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['user_id'])->toBe('123'); // Sanitized to string
            expect($result['action'])->toBe('login');
        });

        it('includes extras', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = (new LogRecord(
                message: 'Test message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            ))->addExtra('request_id', 'req-123');

            $result = $formatter($record);

            expect($result['request_id'])->toBe('req-123');
        });

        it('flattens context and extras', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = (new LogRecord(
                message: 'Test message',
                context: LogContext::withData(['ctx_key' => 'ctx_value']),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            ))->addExtra('extra_key', 'extra_value');

            $result = $formatter($record);

            expect($result['ctx_key'])->toBe('ctx_value');
            expect($result['extra_key'])->toBe('extra_value');
        });
    });

    describe('key sanitization', function () {
        it('removes leading underscores', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData([
                    '_internal' => 'value'
                ]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(isset($result['internal']))->toBeTrue();
        });

        it('makes keys aggregation friendly', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData([
                    'some.nested.key' => 'value1',
                    'another-key' => 'value2'
                ]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(isset($result['some_nested_key']))->toBeTrue();
            expect(isset($result['another_key']))->toBeTrue();
        });
    });

    describe('value sanitization', function () {
        it('handles string values', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData(['text' => 'simple string']),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['text'])->toBe('simple string');
        });

        it('converts numeric values to strings', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData([
                    'integer' => 42,
                    'float' => 3.14
                ]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['integer'])->toBe('42');
            expect($result['float'])->toBe('3.14');
        });

        it('converts boolean values to strings', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData([
                    'success' => true,
                    'failed' => false
                ]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['success'])->toBe('true');
            expect($result['failed'])->toBe('false');
        });

        it('converts null to string', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData(['nullable' => null]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['nullable'])->toBe('null');
        });

        it('converts arrays to JSON strings', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData([
                    'items' => ['item1', 'item2', 'item3']
                ]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['items'])->toBeString();
            expect(str_contains($result['items'], 'item1'))->toBeTrue();
            expect(str_contains($result['items'], 'item2'))->toBeTrue();
        });

        it('converts objects to class name', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $obj = new stdClass();
            $obj->prop = 'value';

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::withData(['object' => $obj]),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['object'])->toBe('stdClass');
        });
    });

    describe('log levels', function () {
        it('formats DEBUG level', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Debug',
                context: LogContext::empty(),
                level: LogLevel::DEBUG,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['level'])->toBe('DEBUG');
        });

        it('formats all log levels correctly', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $levels = [
                LogLevel::DEBUG,
                LogLevel::INFO,
                LogLevel::NOTICE,
                LogLevel::WARNING,
                LogLevel::ERROR,
                LogLevel::CRITICAL,
                LogLevel::ALERT,
                LogLevel::EMERGENCY
            ];

            foreach ($levels as $level) {
                $record = new LogRecord(
                    message: 'Test',
                    context: LogContext::empty(),
                    level: $level,
                    timestamp: $this->timestamp
                );

                $result = $formatter($record);

                expect($result['level'])->toBe($level->getName());
            }
        });
    });

    describe('readonly behavior', function () {
        it('is a readonly class', function () {
            $reflection = new ReflectionClass(StructuredFormatter::class);

            expect($reflection->isReadOnly())->toBeTrue();
        });
    });

    describe('edge cases', function () {
        it('handles empty context and extras', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            // Should still include standard fields
            expect(isset($result['ts']))->toBeTrue();
            expect(isset($result['level']))->toBeTrue();
            expect(isset($result['msg']))->toBeTrue();
        });

        it('handles very long values', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $longValue = str_repeat('x', 10000);

            $record = new LogRecord(
                message: $longValue,
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect($result['msg'])->toHaveLength(10000);
        });

        it('handles unicode characters', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Unicode: 你好 🌍',
                context: LogContext::withData(['key' => 'мир']),
                level: LogLevel::INFO,
                timestamp: $this->timestamp
            );

            $result = $formatter($record);

            expect(str_contains($result['msg'], '你好'))->toBeTrue();
            expect(str_contains($result['msg'], '🌍'))->toBeTrue();
            expect($result['key'])->toBe('мир');
        });

        it('handles null channel', function () {
            $formatter = new StructuredFormatter(format: 'array');

            $record = new LogRecord(
                message: 'Test',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: $this->timestamp,
                channel: null
            );

            $result = $formatter($record);

            // Channel should not be in array if null
            expect(isset($result['channel']))->toBeFalse();
        });
    });
});
