<?php

declare(strict_types=1);

use App\Framework\Exception\ExceptionMetadata;

describe('ExceptionMetadata', function () {
    describe('Factory Methods', function () {
        it('creates default metadata', function () {
            $metadata = ExceptionMetadata::default();

            expect($metadata->skipAggregation)->toBeFalse();
            expect($metadata->skipReporting)->toBeFalse();
            expect($metadata->reported)->toBeFalse();
            expect($metadata->retryAfter)->toBeNull();
        });

        it('creates metadata with retry', function () {
            $metadata = ExceptionMetadata::withRetry(60);

            expect($metadata->retryAfter)->toBe(60);
            expect($metadata->skipAggregation)->toBeFalse();
        });
    });

    describe('Immutability', function () {
        it('withSkipAggregation creates new instance', function () {
            $original = ExceptionMetadata::default();
            $modified = $original->withSkipAggregation();

            expect($original)->not->toBe($modified);
            expect($original->skipAggregation)->toBeFalse();
            expect($modified->skipAggregation)->toBeTrue();
        });

        it('withSkipReporting creates new instance', function () {
            $original = ExceptionMetadata::default();
            $modified = $original->withSkipReporting();

            expect($original->skipReporting)->toBeFalse();
            expect($modified->skipReporting)->toBeTrue();
        });

        it('withSkipAll creates new instance', function () {
            $original = ExceptionMetadata::default();
            $modified = $original->withSkipAll();

            expect($modified->skipAggregation)->toBeTrue();
            expect($modified->skipReporting)->toBeTrue();
        });

        it('markAsReported creates new instance', function () {
            $original = ExceptionMetadata::default();
            $modified = $original->markAsReported();

            expect($original->reported)->toBeFalse();
            expect($modified->reported)->toBeTrue();
        });

        it('withRetryAfter creates new instance', function () {
            $original = ExceptionMetadata::default();
            $modified = $original->withRetryAfter(30);

            expect($original->retryAfter)->toBeNull();
            expect($modified->retryAfter)->toBe(30);
        });
    });

    describe('Behavior Control', function () {
        it('shouldAggregate returns correct value', function () {
            $default = ExceptionMetadata::default();
            $skipped = $default->withSkipAggregation();

            expect($default->shouldAggregate())->toBeTrue();
            expect($skipped->shouldAggregate())->toBeFalse();
        });

        it('shouldReport considers both flags', function () {
            $default = ExceptionMetadata::default();
            $skipReporting = $default->withSkipReporting();
            $reported = $default->markAsReported();

            expect($default->shouldReport())->toBeTrue();
            expect($skipReporting->shouldReport())->toBeFalse();
            expect($reported->shouldReport())->toBeFalse();
        });

        it('wasReported returns correct value', function () {
            $notReported = ExceptionMetadata::default();
            $reported = $notReported->markAsReported();

            expect($notReported->wasReported())->toBeFalse();
            expect($reported->wasReported())->toBeTrue();
        });
    });

    describe('Chaining', function () {
        it('chains multiple modifications', function () {
            $metadata = ExceptionMetadata::default()
                ->withSkipAggregation()
                ->withRetryAfter(60);

            expect($metadata->skipAggregation)->toBeTrue();
            expect($metadata->retryAfter)->toBe(60);
            expect($metadata->skipReporting)->toBeFalse();
        });

        it('preserves existing values when chaining', function () {
            $metadata = ExceptionMetadata::withRetry(30)
                ->withSkipReporting();

            expect($metadata->retryAfter)->toBe(30);
            expect($metadata->skipReporting)->toBeTrue();
            expect($metadata->skipAggregation)->toBeFalse();
        });
    });

    describe('Serialization', function () {
        it('converts to array', function () {
            $metadata = ExceptionMetadata::default()
                ->withSkipAggregation()
                ->withRetryAfter(60);

            $array = $metadata->toArray();

            expect($array)->toBe([
                'skip_aggregation' => true,
                'skip_reporting' => false,
                'reported' => false,
                'retry_after' => 60,
            ]);
        });

        it('creates from array', function () {
            $array = [
                'skip_aggregation' => true,
                'skip_reporting' => true,
                'reported' => false,
                'retry_after' => 30,
            ];

            $metadata = ExceptionMetadata::fromArray($array);

            expect($metadata->skipAggregation)->toBeTrue();
            expect($metadata->skipReporting)->toBeTrue();
            expect($metadata->reported)->toBeFalse();
            expect($metadata->retryAfter)->toBe(30);
        });

        it('handles missing array keys with defaults', function () {
            $metadata = ExceptionMetadata::fromArray([]);

            expect($metadata->skipAggregation)->toBeFalse();
            expect($metadata->skipReporting)->toBeFalse();
            expect($metadata->reported)->toBeFalse();
            expect($metadata->retryAfter)->toBeNull();
        });

        it('roundtrips through array', function () {
            $original = ExceptionMetadata::default()
                ->withSkipAll()
                ->withRetryAfter(120)
                ->markAsReported();

            $array = $original->toArray();
            $restored = ExceptionMetadata::fromArray($array);

            expect($restored->skipAggregation)->toBe($original->skipAggregation);
            expect($restored->skipReporting)->toBe($original->skipReporting);
            expect($restored->reported)->toBe($original->reported);
            expect($restored->retryAfter)->toBe($original->retryAfter);
        });
    });

    describe('Edge Cases', function () {
        it('handles null retry after', function () {
            $metadata = ExceptionMetadata::default()
                ->withRetryAfter(60)
                ->withRetryAfter(null);

            expect($metadata->retryAfter)->toBeNull();
        });

        it('handles zero retry after', function () {
            $metadata = ExceptionMetadata::withRetry(0);

            expect($metadata->retryAfter)->toBe(0);
        });

        it('handles negative retry after', function () {
            $metadata = ExceptionMetadata::withRetry(-1);

            expect($metadata->retryAfter)->toBe(-1);
        });

        it('marking as reported multiple times is idempotent', function () {
            $metadata = ExceptionMetadata::default()
                ->markAsReported()
                ->markAsReported();

            expect($metadata->reported)->toBeTrue();
            expect($metadata->shouldReport())->toBeFalse();
        });
    });

    describe('Integration Scenarios', function () {
        it('supports typical exception lifecycle', function () {
            // 1. Create exception with default metadata
            $metadata = ExceptionMetadata::default();
            expect($metadata->shouldReport())->toBeTrue();
            expect($metadata->shouldAggregate())->toBeTrue();

            // 2. Skip aggregation for specific case
            $metadata = $metadata->withSkipAggregation();
            expect($metadata->shouldAggregate())->toBeFalse();
            expect($metadata->shouldReport())->toBeTrue();

            // 3. Mark as reported after logging
            $metadata = $metadata->markAsReported();
            expect($metadata->wasReported())->toBeTrue();
            expect($metadata->shouldReport())->toBeFalse();
        });

        it('supports retry scenario', function () {
            // 1. Create with retry hint
            $metadata = ExceptionMetadata::withRetry(60);
            expect($metadata->retryAfter)->toBe(60);

            // 2. Update retry time
            $metadata = $metadata->withRetryAfter(30);
            expect($metadata->retryAfter)->toBe(30);

            // 3. Clear retry
            $metadata = $metadata->withRetryAfter(null);
            expect($metadata->retryAfter)->toBeNull();
        });

        it('supports skip all for internal exceptions', function () {
            $metadata = ExceptionMetadata::default()
                ->withSkipAll();

            expect($metadata->shouldAggregate())->toBeFalse();
            expect($metadata->shouldReport())->toBeFalse();
            expect($metadata->wasReported())->toBeFalse();
        });
    });
});
