<?php

declare(strict_types=1);

use App\Framework\Logging\LogLevel;
use App\Framework\Logging\LogProcessor;
use App\Framework\Logging\LogRecord;
use App\Framework\Logging\ProcessorManager;
use App\Framework\Logging\ValueObjects\LogContext;

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

    describe('constructor', function () {
        it('accepts no processors', function () {
            $manager = new ProcessorManager();

            expect($manager instanceof ProcessorManager)->toBeTrue();
        });

        it('accepts single processor', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'test';
                }
            };

            $manager = new ProcessorManager($processor);

            expect($manager instanceof ProcessorManager)->toBeTrue();
        });

        it('accepts multiple processors', function () {
            $processor1 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'test1';
                }
            };

            $processor2 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 20;
                }

                public function getName(): string
                {
                    return 'test2';
                }
            };

            $manager = new ProcessorManager($processor1, $processor2);

            expect($manager instanceof ProcessorManager)->toBeTrue();
        });
    });

    describe('addProcessor()', function () {
        it('returns new instance with added processor', function () {
            $manager = new ProcessorManager();

            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'new_processor';
                }
            };

            $newManager = $manager->addProcessor($processor);

            // Should return new instance (readonly pattern)
            expect($newManager !== $manager)->toBeTrue();
        });

        it('adds processor to existing list', function () {
            $processor1 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'first';
                }
            };

            $manager = new ProcessorManager($processor1);

            $processor2 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 20;
                }

                public function getName(): string
                {
                    return 'second';
                }
            };

            $newManager = $manager->addProcessor($processor2);

            $list = $newManager->getProcessorList();
            expect(count($list))->toBe(2);
            expect(isset($list['first']))->toBeTrue();
            expect(isset($list['second']))->toBeTrue();
        });

        it('maintains priority sorting after adding processor', function () {
            $lowPriority = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('low', true);
                }

                public function getPriority(): int
                {
                    return 5;
                }

                public function getName(): string
                {
                    return 'low';
                }
            };

            $manager = new ProcessorManager($lowPriority);

            $highPriority = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('high', true);
                }

                public function getPriority(): int
                {
                    return 20;
                }

                public function getName(): string
                {
                    return 'high';
                }
            };

            $newManager = $manager->addProcessor($highPriority);

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

            $processed = $newManager->processRecord($record);

            // High priority should run first
            $extras = $processed->getExtras();
            expect(isset($extras['high']))->toBeTrue();
            expect(isset($extras['low']))->toBeTrue();
        });
    });

    describe('processRecord()', function () {
        it('returns original record when no processors', function () {
            $manager = new ProcessorManager();

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

            $processed = $manager->processRecord($record);

            expect($processed->getMessage())->toBe('Test message');
        });

        it('applies single processor to record', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('processed', true);
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'test_processor';
                }
            };

            $manager = new ProcessorManager($processor);

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

            $processed = $manager->processRecord($record);

            $extras = $processed->getExtras();
            expect(isset($extras['processed']))->toBeTrue();
            expect($extras['processed'])->toBe(true);
        });

        it('applies multiple processors in priority order', function () {
            $processor1 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('order', 'first');
                }

                public function getPriority(): int
                {
                    return 20; // Higher priority = runs first
                }

                public function getName(): string
                {
                    return 'high_priority';
                }
            };

            $processor2 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    $extras = $record->getExtras();
                    // Should have 'order' from first processor
                    return $record->addExtra('second_saw_first', isset($extras['order']));
                }

                public function getPriority(): int
                {
                    return 10; // Lower priority = runs second
                }

                public function getName(): string
                {
                    return 'low_priority';
                }
            };

            $manager = new ProcessorManager($processor1, $processor2);

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

            $processed = $manager->processRecord($record);

            $extras = $processed->getExtras();
            expect($extras['order'])->toBe('first');
            expect($extras['second_saw_first'])->toBeTrue();
        });

        it('chains processor transformations', function () {
            $addPrefix = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->withMessage('[PREFIX] ' . $record->getMessage());
                }

                public function getPriority(): int
                {
                    return 20;
                }

                public function getName(): string
                {
                    return 'prefix';
                }
            };

            $addSuffix = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->withMessage($record->getMessage() . ' [SUFFIX]');
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'suffix';
                }
            };

            $manager = new ProcessorManager($addPrefix, $addSuffix);

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

            $processed = $manager->processRecord($record);

            expect($processed->getMessage())->toBe('[PREFIX] Message [SUFFIX]');
        });
    });

    describe('getProcessorList()', function () {
        it('returns empty array when no processors', function () {
            $manager = new ProcessorManager();

            $list = $manager->getProcessorList();

            expect($list)->toBeArray();
            expect($list)->toBeEmpty();
        });

        it('returns array with processor names as keys', function () {
            $processor1 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'first_processor';
                }
            };

            $processor2 = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 20;
                }

                public function getName(): string
                {
                    return 'second_processor';
                }
            };

            $manager = new ProcessorManager($processor1, $processor2);

            $list = $manager->getProcessorList();

            expect(array_key_exists('first_processor', $list))->toBeTrue();
            expect(array_key_exists('second_processor', $list))->toBeTrue();
        });

        it('returns priorities as values', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 42;
                }

                public function getName(): string
                {
                    return 'test';
                }
            };

            $manager = new ProcessorManager($processor);

            $list = $manager->getProcessorList();

            expect($list['test'])->toBe(42);
        });
    });

    describe('hasProcessor()', function () {
        it('returns false when no processors', function () {
            $manager = new ProcessorManager();

            expect($manager->hasProcessor('any_name'))->toBeFalse();
        });

        it('returns true when processor exists', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'existing_processor';
                }
            };

            $manager = new ProcessorManager($processor);

            expect($manager->hasProcessor('existing_processor'))->toBeTrue();
        });

        it('returns false when processor does not exist', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'existing';
                }
            };

            $manager = new ProcessorManager($processor);

            expect($manager->hasProcessor('nonexistent'))->toBeFalse();
        });
    });

    describe('getProcessor()', function () {
        it('returns null when no processors', function () {
            $manager = new ProcessorManager();

            expect($manager->getProcessor('any_name'))->toBeNull();
        });

        it('returns processor when exists', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'findme';
                }
            };

            $manager = new ProcessorManager($processor);

            $found = $manager->getProcessor('findme');

            expect($found !== null)->toBeTrue();
            expect($found->getName())->toBe('findme');
        });

        it('returns null when processor does not exist', function () {
            $processor = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record;
                }

                public function getPriority(): int
                {
                    return 10;
                }

                public function getName(): string
                {
                    return 'existing';
                }
            };

            $manager = new ProcessorManager($processor);

            expect($manager->getProcessor('nonexistent'))->toBeNull();
        });
    });

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

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

    describe('priority sorting', function () {
        it('sorts processors by priority descending', function () {
            $lowPriority = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('low', true);
                }

                public function getPriority(): int
                {
                    return 5;
                }

                public function getName(): string
                {
                    return 'low';
                }
            };

            $mediumPriority = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('medium', true);
                }

                public function getPriority(): int
                {
                    return 15;
                }

                public function getName(): string
                {
                    return 'medium';
                }
            };

            $highPriority = new class implements LogProcessor {
                public function processRecord(LogRecord $record): LogRecord
                {
                    return $record->addExtra('high', true);
                }

                public function getPriority(): int
                {
                    return 25;
                }

                public function getName(): string
                {
                    return 'high';
                }
            };

            // Add in random order
            $manager = new ProcessorManager($lowPriority, $highPriority, $mediumPriority);

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

            $processed = $manager->processRecord($record);

            // All extras should be present (processors were executed)
            $extras = $processed->getExtras();
            expect(isset($extras['high']))->toBeTrue();
            expect(isset($extras['medium']))->toBeTrue();
            expect(isset($extras['low']))->toBeTrue();

            // Verify they all have value true
            expect($extras['high'] === true)->toBeTrue();
            expect($extras['medium'] === true)->toBeTrue();
            expect($extras['low'] === true)->toBeTrue();
        });
    });
});
