<?php

declare(strict_types=1);

use App\Framework\Logging\Handlers\JsonFileHandler;
use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ValueObjects\LogContext;

describe('JsonFileHandler', function () {
    beforeEach(function () {
        // Create test directory
        $this->testDir = sys_get_temp_dir() . '/logger_json_test_' . uniqid();
        mkdir($this->testDir, 0777, true);

        $this->testLogFile = $this->testDir . '/test.json';
    });

    afterEach(function () {
        // Clean up test files
        if (file_exists($this->testLogFile)) {
            unlink($this->testLogFile);
        }
        if (is_dir($this->testDir)) {
            rmdir($this->testDir);
        }
    });

    describe('constructor', function () {
        it('creates log file directory if it does not exist', function () {
            $logFile = $this->testDir . '/nested/dir/test.json';
            $handler = new JsonFileHandler($logFile);

            expect(is_dir(dirname($logFile)))->toBeTrue();
        });

        it('accepts LogLevel enum as minLevel', function () {
            $handler = new JsonFileHandler($this->testLogFile, LogLevel::ERROR);

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::WARNING,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeFalse();
        });

        it('accepts int as minLevel', function () {
            $handler = new JsonFileHandler($this->testLogFile, 400); // ERROR level

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::WARNING,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeFalse();
        });

        it('defaults to INFO level when no minLevel specified', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $infoRecord = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $debugRecord = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::DEBUG,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($infoRecord))->toBeTrue();
            expect($handler->isHandling($debugRecord))->toBeFalse();
        });

        it('uses default fields when no includedFields specified', function () {
            $handler = new JsonFileHandler($this->testLogFile);

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

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            // Neue einheitliche Feldnamen (konsistent mit JsonFormatter)
            expect($json)->toHaveKeys(['timestamp', 'level', 'level_value', 'message', 'context', 'channel']);
        });

        it('uses custom includedFields when specified', function () {
            $handler = new JsonFileHandler(
                $this->testLogFile,
                includedFields: ['level', 'message'] // Neue einheitliche Feldnamen
            );

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

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json)->toHaveKeys(['level', 'message']);
            expect($json)->not->toHaveKey('context');
            expect($json)->not->toHaveKey('channel');
        });
    });

    describe('isHandling()', function () {
        it('returns true when record level is above minLevel', function () {
            $handler = new JsonFileHandler($this->testLogFile, LogLevel::WARNING);

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::ERROR,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeTrue();
        });

        it('returns true when record level equals minLevel', function () {
            $handler = new JsonFileHandler($this->testLogFile, LogLevel::WARNING);

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::WARNING,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeTrue();
        });

        it('returns false when record level is below minLevel', function () {
            $handler = new JsonFileHandler($this->testLogFile, LogLevel::ERROR);

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::WARNING,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeFalse();
        });
    });

    describe('handle()', function () {
        it('writes valid JSON to file', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $record = new LogRecord(
                message: 'Test log message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record);

            expect(file_exists($this->testLogFile))->toBeTrue();
            $content = file_get_contents($this->testLogFile);

            // Check valid JSON
            $json = json_decode(trim($content), true);
            expect($json)->not->toBeNull();
            expect(json_last_error())->toBe(JSON_ERROR_NONE);
        });

        it('includes message in JSON output', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $record = new LogRecord(
                message: 'Important message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json['message'])->toBe('Important message');
        });

        it('includes level in JSON output', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $record = new LogRecord(
                message: 'Error message',
                context: LogContext::empty(),
                level: LogLevel::ERROR,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json['level'])->toBe('ERROR');
        });

        it('includes channel in JSON output', function () {
            $handler = new JsonFileHandler($this->testLogFile);

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

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json['channel'])->toBe('security');
        });

        it('includes context data in JSON output', function () {
            $handler = new JsonFileHandler($this->testLogFile);

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

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            // Context wird geflattened (nur structured data, ohne 'structured' wrapper)
            expect($json['context'])->toMatchArray(['user_id' => 123, 'action' => 'login']);
        });

        it('includes extra data in JSON output', function () {
            $handler = new JsonFileHandler($this->testLogFile);

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

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json['extra'])->toBe(['request_id' => 'req-123']);
        });

        it('appends each log entry on new line', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $record1 = new LogRecord(
                message: 'First message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $record2 = new LogRecord(
                message: 'Second message',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record1);
            $handler->handle($record2);

            $content = file_get_contents($this->testLogFile);
            $lines = explode(PHP_EOL, trim($content));

            expect(count($lines))->toBe(2);

            $json1 = json_decode($lines[0], true);
            $json2 = json_decode($lines[1], true);

            expect($json1['message'])->toBe('First message');
            expect($json2['message'])->toBe('Second message');
        });

        it('handles unicode characters correctly', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $record = new LogRecord(
                message: 'Übung mit Ümläüten und 日本語',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json['message'])->toBe('Übung mit Ümläüten und 日本語');
        });

        it('only includes specified fields when includedFields is set', function () {
            $handler = new JsonFileHandler(
                $this->testLogFile,
                includedFields: ['level', 'message'] // Neue einheitliche Feldnamen
            );

            $record = (new LogRecord(
                message: 'Test message',
                context: LogContext::withData(['key' => 'value']),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            ))->withExtra('extra_key', 'extra_value'); // Immutable API

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect(array_keys($json))->toBe(['level', 'message']);
        });
    });

    describe('setMinLevel()', function () {
        it('updates minimum level with LogLevel', function () {
            $handler = new JsonFileHandler($this->testLogFile, LogLevel::DEBUG);
            $handler->setMinLevel(LogLevel::ERROR);

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::WARNING,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeFalse();
        });

        it('updates minimum level with int', function () {
            $handler = new JsonFileHandler($this->testLogFile, LogLevel::DEBUG);
            $handler->setMinLevel(400); // ERROR

            $record = new LogRecord(
                message: 'test message',
                context: LogContext::empty(),
                level: LogLevel::WARNING,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeFalse();
        });

        it('returns self for fluent API', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $result = $handler->setMinLevel(LogLevel::ERROR);

            expect($result)->toBe($handler);
        });
    });

    describe('setIncludedFields()', function () {
        it('updates included fields', function () {
            $handler = new JsonFileHandler($this->testLogFile);
            $handler->setIncludedFields(['message']);

            $record = new LogRecord(
                message: 'Only message',
                context: LogContext::withData(['should_not_appear' => 'value']),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect(array_keys($json))->toBe(['message']);
        });

        it('returns self for fluent API', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $result = $handler->setIncludedFields(['message']);

            expect($result)->toBe($handler);
        });
    });

    describe('setLogFile()', function () {
        it('updates log file path', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $newLogFile = $this->testDir . '/new-test.json';
            $handler->setLogFile($newLogFile);

            $record = new LogRecord(
                message: 'New file test',
                context: LogContext::empty(),
                level: LogLevel::INFO,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            $handler->handle($record);

            expect(file_exists($newLogFile))->toBeTrue();

            // Cleanup
            if (file_exists($newLogFile)) {
                unlink($newLogFile);
            }
        });

        it('returns self for fluent API', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $result = $handler->setLogFile($this->testDir . '/other.json');

            expect($result)->toBe($handler);
        });
    });

    describe('fluent interface', function () {
        it('supports chaining methods', function () {
            $handler = new JsonFileHandler($this->testLogFile);

            $handler
                ->setMinLevel(LogLevel::WARNING)
                ->setIncludedFields(['level', 'message']) // Neue einheitliche Feldnamen
                ->setLogFile($this->testLogFile);

            $record = new LogRecord(
                message: 'Chained config',
                context: LogContext::empty(),
                level: LogLevel::ERROR,
                timestamp: new DateTimeImmutable(),
                channel: 'test'
            );

            expect($handler->isHandling($record))->toBeTrue();
            $handler->handle($record);

            $content = file_get_contents($this->testLogFile);
            $json = json_decode(trim($content), true);

            expect($json)->toBe([
                'level' => 'ERROR',
                'message' => 'Chained config',
            ]);
        });
    });
});
