<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Queue\ValueObjects\JobProgress;

describe('JobProgress Value Object', function () {

    describe('Basic Construction and Validation', function () {
        it('can create job progress with percentage and message', function () {
            $percentage = Percentage::fromValue(50.0);
            $message = 'Processing half way complete';
            $metadata = ['step' => 'validation', 'items_processed' => 500];

            $progress = JobProgress::withPercentage($percentage, $message, $metadata);

            expect($progress->percentage)->toBe($percentage);
            expect($progress->message)->toBe($message);
            expect($progress->metadata)->toBe($metadata);
        });

        it('rejects empty progress messages', function () {
            expect(fn () => JobProgress::withPercentage(Percentage::zero(), ''))
                ->toThrow(\InvalidArgumentException::class, 'Progress message cannot be empty');

            expect(fn () => JobProgress::withPercentage(Percentage::zero(), '   '))
                ->toThrow(\InvalidArgumentException::class, 'Progress message cannot be empty');
        });

        it('is readonly and immutable', function () {
            $progress = JobProgress::starting('Test job starting');

            $reflection = new ReflectionClass($progress);
            expect($reflection->isReadOnly())->toBeTrue();

            // All properties should be readonly
            foreach (['percentage', 'message', 'metadata'] as $prop) {
                $property = $reflection->getProperty($prop);
                expect($property->isReadOnly())->toBeTrue("Property {$prop} should be readonly");
            }
        });
    });

    describe('Factory Methods', function () {
        it('creates starting progress', function () {
            $progress = JobProgress::starting();

            expect($progress->percentage->getValue())->toBe(0.0);
            expect($progress->message)->toBe('Job starting...');
            expect($progress->isStarting())->toBeTrue();
            expect($progress->isCompleted())->toBeFalse();
            expect($progress->isFailed())->toBeFalse();
        });

        it('creates starting progress with custom message', function () {
            $message = 'Email job initializing...';
            $progress = JobProgress::starting($message);

            expect($progress->percentage->getValue())->toBe(0.0);
            expect($progress->message)->toBe($message);
            expect($progress->isStarting())->toBeTrue();
        });

        it('creates completed progress', function () {
            $progress = JobProgress::completed();

            expect($progress->percentage->getValue())->toBe(100.0);
            expect($progress->message)->toBe('Job completed successfully');
            expect($progress->isCompleted())->toBeTrue();
            expect($progress->isStarting())->toBeFalse();
            expect($progress->isFailed())->toBeFalse();
        });

        it('creates completed progress with custom message', function () {
            $message = 'All emails sent successfully';
            $progress = JobProgress::completed($message);

            expect($progress->percentage->getValue())->toBe(100.0);
            expect($progress->message)->toBe($message);
            expect($progress->isCompleted())->toBeTrue();
        });

        it('creates failed progress', function () {
            $progress = JobProgress::failed();

            expect($progress->percentage->getValue())->toBe(0.0);
            expect($progress->message)->toBe('Job failed');
            expect($progress->isFailed())->toBeTrue();
            expect($progress->isCompleted())->toBeFalse();
            expect($progress->isStarting())->toBeFalse();
            expect($progress->metadata['status'])->toBe('failed');
        });

        it('creates failed progress with custom message', function () {
            $message = 'Email service unavailable';
            $progress = JobProgress::failed($message);

            expect($progress->message)->toBe($message);
            expect($progress->isFailed())->toBeTrue();
        });

        it('creates progress from ratio', function () {
            $progress = JobProgress::fromRatio(25, 100, 'Processing items', ['current_item' => 25]);

            expect($progress->percentage->getValue())->toBe(25.0);
            expect($progress->message)->toBe('Processing items');
            expect($progress->metadata['current_item'])->toBe(25);
        });

        it('handles edge cases in fromRatio', function () {
            // Zero total
            $progress = JobProgress::fromRatio(0, 0, 'No items to process');
            expect($progress->percentage->getValue())->toBe(0.0);

            // All items processed
            $progress = JobProgress::fromRatio(100, 100, 'All items processed');
            expect($progress->percentage->getValue())->toBe(100.0);
            expect($progress->isCompleted())->toBeTrue();
        });
    });

    describe('Status Check Methods', function () {
        it('correctly identifies completed status', function () {
            $completed = JobProgress::completed();
            $partial = JobProgress::withPercentage(Percentage::fromValue(50.0), 'Half done');
            $starting = JobProgress::starting();

            expect($completed->isCompleted())->toBeTrue();
            expect($partial->isCompleted())->toBeFalse();
            expect($starting->isCompleted())->toBeFalse();
        });

        it('correctly identifies failed status', function () {
            $failed = JobProgress::failed();
            $completed = JobProgress::completed();
            $starting = JobProgress::starting();

            expect($failed->isFailed())->toBeTrue();
            expect($completed->isFailed())->toBeFalse();
            expect($starting->isFailed())->toBeFalse();
        });

        it('correctly identifies starting status', function () {
            $starting = JobProgress::starting();
            $partial = JobProgress::withPercentage(Percentage::fromValue(10.0), 'Just started');
            $failed = JobProgress::failed();
            $completed = JobProgress::completed();

            expect($starting->isStarting())->toBeTrue();
            expect($partial->isStarting())->toBeFalse();
            expect($failed->isStarting())->toBeFalse(); // Failed is not starting
            expect($completed->isStarting())->toBeFalse();
        });

        it('handles edge cases in status detection', function () {
            // Zero percentage but not starting due to metadata
            $zeroButNotStarting = JobProgress::withPercentage(
                Percentage::zero(),
                'Waiting for dependencies',
                ['status' => 'waiting']
            );
            expect($zeroButNotStarting->isStarting())->toBeTrue(); // Still starting since not failed

            // Custom failed status
            $customFailed = JobProgress::withPercentage(
                Percentage::fromValue(50.0),
                'Failed during processing',
                ['status' => 'failed']
            );
            expect($customFailed->isFailed())->toBeTrue();
            expect($customFailed->isCompleted())->toBeFalse();
        });
    });

    describe('Immutable Transformations', function () {
        beforeEach(function () {
            $this->originalProgress = JobProgress::withPercentage(
                Percentage::fromValue(25.0),
                'Quarter complete',
                ['step' => 'validation']
            );
        });

        it('withMetadata() creates new instance with merged metadata', function () {
            $newMetadata = ['items_processed' => 250, 'errors' => 0];
            $updated = $this->originalProgress->withMetadata($newMetadata);

            expect($updated)->not->toBe($this->originalProgress);
            expect($updated->percentage)->toBe($this->originalProgress->percentage);
            expect($updated->message)->toBe($this->originalProgress->message);
            expect($updated->metadata['step'])->toBe('validation'); // Original metadata preserved
            expect($updated->metadata['items_processed'])->toBe(250); // New metadata added
            expect($updated->metadata['errors'])->toBe(0);

            // Original should be unchanged
            expect($this->originalProgress->metadata)->toBe(['step' => 'validation']);
        });

        it('withMetadata() overwrites conflicting keys', function () {
            $newMetadata = ['step' => 'processing']; // Conflicts with existing key
            $updated = $this->originalProgress->withMetadata($newMetadata);

            expect($updated->metadata['step'])->toBe('processing'); // New value wins
        });

        it('withUpdatedProgress() creates new instance with updated progress', function () {
            $newPercentage = Percentage::fromValue(75.0);
            $newMessage = 'Three quarters complete';
            $updated = $this->originalProgress->withUpdatedProgress($newPercentage, $newMessage);

            expect($updated)->not->toBe($this->originalProgress);
            expect($updated->percentage)->toBe($newPercentage);
            expect($updated->message)->toBe($newMessage);
            expect($updated->metadata)->toBe($this->originalProgress->metadata); // Metadata preserved

            // Original should be unchanged
            expect($this->originalProgress->percentage->getValue())->toBe(25.0);
            expect($this->originalProgress->message)->toBe('Quarter complete');
        });

        it('can chain transformations', function () {
            $final = $this->originalProgress
                ->withUpdatedProgress(Percentage::fromValue(50.0), 'Half complete')
                ->withMetadata(['processed_items' => 500]);

            expect($final->percentage->getValue())->toBe(50.0);
            expect($final->message)->toBe('Half complete');
            expect($final->metadata['step'])->toBe('validation'); // Original preserved
            expect($final->metadata['processed_items'])->toBe(500); // New added

            // Original should be completely unchanged
            expect($this->originalProgress->percentage->getValue())->toBe(25.0);
            expect($this->originalProgress->message)->toBe('Quarter complete');
            expect($this->originalProgress->metadata)->toBe(['step' => 'validation']);
        });
    });

    describe('Array Conversion', function () {
        it('toArray() provides comprehensive progress information', function () {
            $progress = JobProgress::withPercentage(
                Percentage::fromValue(75.5),
                'Processing emails',
                ['batch_id' => 123, 'errors' => 2]
            );

            $array = $progress->toArray();

            expect($array)->toHaveKey('percentage');
            expect($array)->toHaveKey('percentage_formatted');
            expect($array)->toHaveKey('message');
            expect($array)->toHaveKey('metadata');
            expect($array)->toHaveKey('is_completed');
            expect($array)->toHaveKey('is_failed');
            expect($array)->toHaveKey('is_starting');

            expect($array['percentage'])->toBe(75.5);
            expect($array['percentage_formatted'])->toBe('75.5%');
            expect($array['message'])->toBe('Processing emails');
            expect($array['metadata'])->toBe(['batch_id' => 123, 'errors' => 2]);
            expect($array['is_completed'])->toBeFalse();
            expect($array['is_failed'])->toBeFalse();
            expect($array['is_starting'])->toBeFalse();
        });

        it('toArray() handles different progress states', function () {
            $states = [
                'starting' => JobProgress::starting(),
                'completed' => JobProgress::completed(),
                'failed' => JobProgress::failed(),
            ];

            foreach ($states as $stateName => $progress) {
                $array = $progress->toArray();

                expect($array)->toBeArray();
                expect($array)->toHaveKey('is_completed');
                expect($array)->toHaveKey('is_failed');
                expect($array)->toHaveKey('is_starting');

                switch ($stateName) {
                    case 'starting':
                        expect($array['is_starting'])->toBeTrue();
                        expect($array['is_completed'])->toBeFalse();
                        expect($array['is_failed'])->toBeFalse();

                        break;
                    case 'completed':
                        expect($array['is_completed'])->toBeTrue();
                        expect($array['is_starting'])->toBeFalse();
                        expect($array['is_failed'])->toBeFalse();

                        break;
                    case 'failed':
                        expect($array['is_failed'])->toBeTrue();
                        expect($array['is_starting'])->toBeFalse();
                        expect($array['is_completed'])->toBeFalse();

                        break;
                }
            }
        });
    });
});

describe('Job Progress Tracking System Mock', function () {

    beforeEach(function () {
        // Create a mock progress tracker for testing
        $this->progressTracker = new class () {
            private array $progressEntries = [];

            public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
            {
                $this->progressEntries[$jobId][] = [
                    'progress' => $progress,
                    'step_name' => $stepName,
                    'timestamp' => time(),
                    'id' => uniqid(),
                ];
            }

            public function getCurrentProgress(string $jobId): ?JobProgress
            {
                if (! isset($this->progressEntries[$jobId]) || empty($this->progressEntries[$jobId])) {
                    return null;
                }

                $entries = $this->progressEntries[$jobId];

                return end($entries)['progress'];
            }

            public function getProgressHistory(string $jobId): array
            {
                return $this->progressEntries[$jobId] ?? [];
            }

            public function markJobCompleted(string $jobId, string $message = 'Job completed successfully'): void
            {
                $this->updateProgress($jobId, JobProgress::completed($message));
            }

            public function markJobFailed(string $jobId, string $message = 'Job failed', ?\Throwable $exception = null): void
            {
                $metadata = [];
                if ($exception) {
                    $metadata['exception_type'] = get_class($exception);
                    $metadata['exception_message'] = $exception->getMessage();
                }

                $progress = JobProgress::failed($message)->withMetadata($metadata);
                $this->updateProgress($jobId, $progress);
            }

            public function getProgressForJobs(array $jobIds): array
            {
                $result = [];
                foreach ($jobIds as $jobId) {
                    $current = $this->getCurrentProgress($jobId);
                    if ($current !== null) {
                        $result[$jobId] = $current;
                    }
                }

                return $result;
            }

            public function getJobsAboveProgress(float $minPercentage): array
            {
                $result = [];
                foreach ($this->progressEntries as $jobId => $entries) {
                    $current = end($entries)['progress'];
                    if ($current->percentage->getValue() >= $minPercentage) {
                        $result[] = ['job_id' => $jobId, 'progress' => $current];
                    }
                }

                return $result;
            }
        };
    });

    describe('Progress Tracking Operations', function () {
        it('can track job progress updates', function () {
            $jobId = JobId::generate()->toString();

            // Track job progression
            $this->progressTracker->updateProgress($jobId, JobProgress::starting('Job initialized'));
            $this->progressTracker->updateProgress($jobId, JobProgress::fromRatio(25, 100, 'Processing batch 1'));
            $this->progressTracker->updateProgress($jobId, JobProgress::fromRatio(50, 100, 'Processing batch 2'));
            $this->progressTracker->updateProgress($jobId, JobProgress::fromRatio(75, 100, 'Processing batch 3'));
            $this->progressTracker->markJobCompleted($jobId, 'All batches processed');

            $history = $this->progressTracker->getProgressHistory($jobId);
            expect(count($history))->toBe(5);

            $current = $this->progressTracker->getCurrentProgress($jobId);
            expect($current->isCompleted())->toBeTrue();
            expect($current->message)->toBe('All batches processed');
        });

        it('can track job with steps', function () {
            $jobId = JobId::generate()->toString();

            $steps = [
                'validation' => 'Validating input data',
                'processing' => 'Processing records',
                'notification' => 'Sending notifications',
                'cleanup' => 'Cleaning up temporary files',
            ];

            foreach ($steps as $stepName => $message) {
                $this->progressTracker->updateProgress(
                    $jobId,
                    JobProgress::fromRatio(array_search($stepName, array_keys($steps)) + 1, count($steps), $message),
                    $stepName
                );
            }

            $history = $this->progressTracker->getProgressHistory($jobId);
            expect(count($history))->toBe(4);

            // Check step names are tracked
            $stepNames = array_map(fn ($entry) => $entry['step_name'], $history);
            expect($stepNames)->toBe(['validation', 'processing', 'notification', 'cleanup']);
        });

        it('can mark jobs as failed with exception details', function () {
            $jobId = JobId::generate()->toString();

            $this->progressTracker->updateProgress($jobId, JobProgress::starting('Starting email job'));
            $this->progressTracker->updateProgress($jobId, JobProgress::fromRatio(10, 100, 'Connecting to email service'));

            // Simulate failure with exception
            $exception = new \RuntimeException('Email service unavailable');
            $this->progressTracker->markJobFailed($jobId, 'Failed to connect to email service', $exception);

            $current = $this->progressTracker->getCurrentProgress($jobId);
            expect($current->isFailed())->toBeTrue();
            expect($current->message)->toBe('Failed to connect to email service');
            expect($current->metadata['exception_type'])->toBe('RuntimeException');
            expect($current->metadata['exception_message'])->toBe('Email service unavailable');
        });

        it('handles jobs with no progress', function () {
            $nonExistentJobId = JobId::generate()->toString();

            $current = $this->progressTracker->getCurrentProgress($nonExistentJobId);
            expect($current)->toBeNull();

            $history = $this->progressTracker->getProgressHistory($nonExistentJobId);
            expect($history)->toBe([]);
        });
    });

    describe('Bulk Progress Operations', function () {
        it('can get progress for multiple jobs', function () {
            $jobIds = [
                JobId::generate()->toString(),
                JobId::generate()->toString(),
                JobId::generate()->toString(),
            ];

            // Add progress for some jobs
            $this->progressTracker->updateProgress($jobIds[0], JobProgress::fromRatio(25, 100, 'Job 1 progress'));
            $this->progressTracker->updateProgress($jobIds[1], JobProgress::fromRatio(75, 100, 'Job 2 progress'));
            // Job 3 has no progress

            $bulkProgress = $this->progressTracker->getProgressForJobs($jobIds);

            expect(count($bulkProgress))->toBe(2);
            expect($bulkProgress[$jobIds[0]]->percentage->getValue())->toBe(25.0);
            expect($bulkProgress[$jobIds[1]]->percentage->getValue())->toBe(75.0);
            expect(isset($bulkProgress[$jobIds[2]]))->toBeFalse();
        });

        it('can find jobs above certain progress threshold', function () {
            $jobs = [
                JobId::generate()->toString() => 10.0,
                JobId::generate()->toString() => 50.0,
                JobId::generate()->toString() => 80.0,
                JobId::generate()->toString() => 95.0,
            ];

            foreach ($jobs as $jobId => $progress) {
                $this->progressTracker->updateProgress(
                    $jobId,
                    JobProgress::fromRatio((int)$progress, 100, "Progress at {$progress}%")
                );
            }

            $jobsAbove60 = $this->progressTracker->getJobsAboveProgress(60.0);
            expect(count($jobsAbove60))->toBe(2); // 80% and 95%

            $jobsAbove90 = $this->progressTracker->getJobsAboveProgress(90.0);
            expect(count($jobsAbove90))->toBe(1); // Only 95%

            $jobsAbove100 = $this->progressTracker->getJobsAboveProgress(100.0);
            expect(count($jobsAbove100))->toBe(0); // None at 100%
        });

        it('handles empty job lists gracefully', function () {
            $emptyResult = $this->progressTracker->getProgressForJobs([]);
            expect($emptyResult)->toBe([]);

            $noJobs = $this->progressTracker->getJobsAboveProgress(50.0);
            expect($noJobs)->toBe([]);
        });
    });

    describe('Progress Tracking Edge Cases', function () {
        it('handles rapid progress updates', function () {
            $jobId = JobId::generate()->toString();

            // Simulate rapid updates
            for ($i = 0; $i <= 100; $i += 10) {
                $this->progressTracker->updateProgress(
                    $jobId,
                    JobProgress::fromRatio($i, 100, "Progress at {$i}%")
                );
            }

            $history = $this->progressTracker->getProgressHistory($jobId);
            expect(count($history))->toBe(11); // 0, 10, 20, ..., 100

            $current = $this->progressTracker->getCurrentProgress($jobId);
            expect($current->isCompleted())->toBeTrue();
        });

        it('maintains progress order', function () {
            $jobId = JobId::generate()->toString();

            $progressUpdates = [
                ['percentage' => 0, 'message' => 'Starting'],
                ['percentage' => 25, 'message' => 'Quarter done'],
                ['percentage' => 50, 'message' => 'Half done'],
                ['percentage' => 75, 'message' => 'Three quarters done'],
                ['percentage' => 100, 'message' => 'Completed'],
            ];

            foreach ($progressUpdates as $update) {
                $this->progressTracker->updateProgress(
                    $jobId,
                    JobProgress::fromRatio($update['percentage'], 100, $update['message'])
                );
                // Small delay to ensure different timestamps
                usleep(1000);
            }

            $history = $this->progressTracker->getProgressHistory($jobId);
            $messages = array_map(fn ($entry) => $entry['progress']->message, $history);

            expect($messages)->toBe([
                'Starting',
                'Quarter done',
                'Half done',
                'Three quarters done',
                'Completed',
            ]);
        });

        it('handles concurrent job tracking', function () {
            $jobIds = [
                'job_a' => JobId::generate()->toString(),
                'job_b' => JobId::generate()->toString(),
                'job_c' => JobId::generate()->toString(),
            ];

            // Simulate concurrent progress updates
            foreach ($jobIds as $label => $jobId) {
                $this->progressTracker->updateProgress($jobId, JobProgress::starting("Starting {$label}"));
            }

            foreach ($jobIds as $label => $jobId) {
                $this->progressTracker->updateProgress($jobId, JobProgress::fromRatio(50, 100, "{$label} half done"));
            }

            // Complete jobs at different times
            $this->progressTracker->markJobCompleted($jobIds['job_a'], 'Job A completed');
            $this->progressTracker->markJobFailed($jobIds['job_b'], 'Job B failed');
            $this->progressTracker->markJobCompleted($jobIds['job_c'], 'Job C completed');

            // Verify independent tracking
            $progressA = $this->progressTracker->getCurrentProgress($jobIds['job_a']);
            $progressB = $this->progressTracker->getCurrentProgress($jobIds['job_b']);
            $progressC = $this->progressTracker->getCurrentProgress($jobIds['job_c']);

            expect($progressA->isCompleted())->toBeTrue();
            expect($progressB->isFailed())->toBeTrue();
            expect($progressC->isCompleted())->toBeTrue();

            // Each job should have its own history
            expect(count($this->progressTracker->getProgressHistory($jobIds['job_a'])))->toBe(3);
            expect(count($this->progressTracker->getProgressHistory($jobIds['job_b'])))->toBe(3);
            expect(count($this->progressTracker->getProgressHistory($jobIds['job_c'])))->toBe(3);
        });
    });
});

describe('Job Progress Integration Scenarios', function () {

    beforeEach(function () {
        $this->emailJob = new class () {
            public function __construct(
                public array $recipients = ['test@example.com'],
                public string $subject = 'Test Email',
                public string $template = 'newsletter'
            ) {
            }

            public function getRecipientCount(): int
            {
                return count($this->recipients);
            }
        };

        $this->reportJob = new class () {
            public function __construct(
                public string $reportType = 'sales',
                public array $criteria = ['period' => 'monthly'],
                public int $totalSteps = 5
            ) {
            }

            public function getSteps(): array
            {
                return [
                    'data_collection' => 'Collecting data from database',
                    'data_processing' => 'Processing and aggregating data',
                    'chart_generation' => 'Generating charts and graphs',
                    'pdf_creation' => 'Creating PDF document',
                    'distribution' => 'Distributing report to stakeholders',
                ];
            }
        };
    });

    it('demonstrates email job progress tracking', function () {
        $jobId = JobId::generate()->toString();
        $progressTracker = new class () {
            private array $progressEntries = [];

            public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
            {
                $this->progressEntries[$jobId][] = ['progress' => $progress, 'step_name' => $stepName];
            }

            public function getCurrentProgress(string $jobId): ?JobProgress
            {
                if (! isset($this->progressEntries[$jobId])) {
                    return null;
                }

                return end($this->progressEntries[$jobId])['progress'];
            }
        };

        $totalRecipients = count($this->emailJob->recipients);

        // Start email job
        $progressTracker->updateProgress(
            $jobId,
            JobProgress::starting('Initializing email job'),
            'initialization'
        );

        // Template preparation
        $progressTracker->updateProgress(
            $jobId,
            JobProgress::fromRatio(1, 4, 'Preparing email template'),
            'template_preparation'
        );

        // Recipient validation
        $progressTracker->updateProgress(
            $jobId,
            JobProgress::fromRatio(2, 4, 'Validating recipient addresses'),
            'recipient_validation'
        );

        // Email sending
        $progressTracker->updateProgress(
            $jobId,
            JobProgress::fromRatio(3, 4, 'Sending emails'),
            'email_sending'
        );

        // Completion
        $progressTracker->updateProgress(
            $jobId,
            JobProgress::completed('All emails sent successfully'),
            'completion'
        );

        $finalProgress = $progressTracker->getCurrentProgress($jobId);
        expect($finalProgress->isCompleted())->toBeTrue();
        expect($finalProgress->message)->toBe('All emails sent successfully');
    });

    it('demonstrates report generation progress tracking', function () {
        $jobId = JobId::generate()->toString();
        $progressTracker = new class () {
            private array $progressEntries = [];

            public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
            {
                $this->progressEntries[$jobId][] = ['progress' => $progress, 'step_name' => $stepName];
            }

            public function getProgressHistory(string $jobId): array
            {
                return $this->progressEntries[$jobId] ?? [];
            }
        };

        $steps = $this->reportJob->getSteps();
        $totalSteps = count($steps);
        $currentStep = 0;

        foreach ($steps as $stepName => $description) {
            $currentStep++;
            $progressTracker->updateProgress(
                $jobId,
                JobProgress::fromRatio($currentStep, $totalSteps, $description),
                $stepName
            );
        }

        $history = $progressTracker->getProgressHistory($jobId);
        expect(count($history))->toBe($totalSteps);

        // Verify step progression
        $stepNames = array_map(fn ($entry) => $entry['step_name'], $history);
        expect($stepNames)->toBe(array_keys($steps));

        // Verify progress percentages
        $percentages = array_map(fn ($entry) => $entry['progress']->percentage->getValue(), $history);
        expect($percentages)->toBe([20.0, 40.0, 60.0, 80.0, 100.0]);
    });

    it('demonstrates error handling with progress tracking', function () {
        $jobId = JobId::generate()->toString();
        $progressTracker = new class () {
            private array $progressEntries = [];

            public function updateProgress(string $jobId, JobProgress $progress, ?string $stepName = null): void
            {
                $this->progressEntries[$jobId][] = ['progress' => $progress, 'step_name' => $stepName];
            }

            public function getCurrentProgress(string $jobId): ?JobProgress
            {
                if (! isset($this->progressEntries[$jobId])) {
                    return null;
                }

                return end($this->progressEntries[$jobId])['progress'];
            }
        };

        // Start processing
        $progressTracker->updateProgress(
            $jobId,
            JobProgress::starting('Starting data processing job'),
            'initialization'
        );

        $progressTracker->updateProgress(
            $jobId,
            JobProgress::fromRatio(1, 3, 'Loading data from database'),
            'data_loading'
        );

        // Simulate error during processing
        $exception = new \RuntimeException('Database connection lost');
        $failedProgress = JobProgress::failed('Processing failed due to database error')
            ->withMetadata([
                'exception_type' => get_class($exception),
                'exception_message' => $exception->getMessage(),
                'failed_at_step' => 'data_processing',
                'items_processed' => 150,
                'total_items' => 500,
            ]);

        $progressTracker->updateProgress($jobId, $failedProgress, 'data_processing');

        $currentProgress = $progressTracker->getCurrentProgress($jobId);
        expect($currentProgress->isFailed())->toBeTrue();
        expect($currentProgress->metadata['items_processed'])->toBe(150);
        expect($currentProgress->metadata['exception_type'])->toBe('RuntimeException');
    });
});
