<?php

declare(strict_types=1);

use App\Framework\Database\Services\SqlStateErrorMapper;
use App\Framework\Database\ValueObjects\SqlState;
use App\Framework\Exception\Core\DatabaseErrorCode;

describe('SqlStateErrorMapper Service', function () {
    beforeEach(function () {
        $this->mapper = new SqlStateErrorMapper();
    });

    describe('mapToErrorCode()', function () {
        it('maps connection errors correctly', function () {
            $testCases = [
                '08001' => DatabaseErrorCode::CONNECTION_FAILED,
                '08003' => DatabaseErrorCode::CONNECTION_FAILED,
                '08004' => DatabaseErrorCode::CONNECTION_FAILED,
                '08006' => DatabaseErrorCode::CONNECTION_FAILED,
            ];

            foreach ($testCases as $sqlStateCode => $expectedErrorCode) {
                $sqlState = new SqlState($sqlStateCode);
                $errorCode = $this->mapper->mapToErrorCode($sqlState);
                expect($errorCode)->toBe($expectedErrorCode);
            }
        });

        it('maps constraint violations correctly', function () {
            $codes = ['23000', '23502', '23503', '23505', '23514'];

            foreach ($codes as $code) {
                $sqlState = new SqlState($code);
                $errorCode = $this->mapper->mapToErrorCode($sqlState);
                expect($errorCode)->toBe(DatabaseErrorCode::CONSTRAINT_VIOLATION);
            }
        });

        it('maps transaction errors correctly', function () {
            $deadlock = new SqlState('40001');
            expect($this->mapper->mapToErrorCode($deadlock))
                ->toBe(DatabaseErrorCode::DEADLOCK_DETECTED);

            $transactionError = new SqlState('40002');
            expect($this->mapper->mapToErrorCode($transactionError))
                ->toBe(DatabaseErrorCode::TRANSACTION_FAILED);
        });

        it('maps syntax errors correctly', function () {
            $codes = ['42000', '42501', '42601', '42P01', '42703', '42S02', '42S22'];

            foreach ($codes as $code) {
                $sqlState = new SqlState($code);
                $errorCode = $this->mapper->mapToErrorCode($sqlState);
                expect($errorCode)->toBe(DatabaseErrorCode::QUERY_SYNTAX_ERROR);
            }
        });

        it('maps data exceptions correctly', function () {
            $codes = ['22001', '22003', '22007', '22012', '22P02'];

            foreach ($codes as $code) {
                $sqlState = new SqlState($code);
                $errorCode = $this->mapper->mapToErrorCode($sqlState);
                expect($errorCode)->toBe(DatabaseErrorCode::QUERY_EXECUTION_FAILED);
            }
        });

        it('falls back to class-based mapping for unknown codes', function () {
            // Unknown code in connection class
            $unknownConnection = new SqlState('08999');
            expect($this->mapper->mapToErrorCode($unknownConnection))
                ->toBe(DatabaseErrorCode::CONNECTION_FAILED);

            // Unknown code in constraint class
            $unknownConstraint = new SqlState('23999');
            expect($this->mapper->mapToErrorCode($unknownConstraint))
                ->toBe(DatabaseErrorCode::CONSTRAINT_VIOLATION);

            // Unknown code in transaction class
            $unknownTransaction = new SqlState('40999');
            expect($this->mapper->mapToErrorCode($unknownTransaction))
                ->toBe(DatabaseErrorCode::TRANSACTION_FAILED);

            // Unknown code in syntax class
            $unknownSyntax = new SqlState('42999');
            expect($this->mapper->mapToErrorCode($unknownSyntax))
                ->toBe(DatabaseErrorCode::QUERY_SYNTAX_ERROR);
        });
    });

    describe('getHumanReadableMessage()', function () {
        it('provides user-friendly messages for connection errors', function () {
            $sqlState = new SqlState('08001');
            $message = $this->mapper->getHumanReadableMessage($sqlState);

            expect($message)->toContain('Unable to establish database connection');
            expect($message)->toContain('Please check server status');
        });

        it('provides user-friendly messages for constraint violations', function () {
            $uniqueViolation = new SqlState('23505');
            $message = $this->mapper->getHumanReadableMessage($uniqueViolation);

            expect($message)->toContain('Duplicate entry');
            expect($message)->toContain('already exists');
        });

        it('provides user-friendly messages for deadlocks', function () {
            $deadlock = new SqlState('40001');
            $message = $this->mapper->getHumanReadableMessage($deadlock);

            expect($message)->toContain('Deadlock detected');
            expect($message)->toContain('retry');
        });

        it('provides user-friendly messages for syntax errors', function () {
            $tableNotFound = new SqlState('42S02');
            $message = $this->mapper->getHumanReadableMessage($tableNotFound);

            expect($message)->toContain('table not found');
            expect($message)->toContain('schema');
        });

        it('provides generic message for unknown codes', function () {
            $unknownCode = new SqlState('99999');
            $message = $this->mapper->getHumanReadableMessage($unknownCode);

            expect($message)->toContain('Database error occurred');
            expect($message)->toContain('99999');
        });

        it('includes SQLSTATE code in fallback messages', function () {
            $unknownCode = new SqlState('88888');
            $message = $this->mapper->getHumanReadableMessage($unknownCode);

            expect($message)->toContain('88888');
        });
    });

    describe('getTechnicalDetails()', function () {
        it('provides complete technical information', function () {
            $sqlState = new SqlState('23505');
            $details = $this->mapper->getTechnicalDetails($sqlState);

            expect($details)->toHaveKey('sqlstate');
            expect($details)->toHaveKey('class');
            expect($details)->toHaveKey('subclass');
            expect($details)->toHaveKey('category');
            expect($details)->toHaveKey('error_code');

            expect($details['sqlstate'])->toBe('23505');
            expect($details['class'])->toBe('23');
            expect($details['subclass'])->toBe('505');
            expect($details['category'])->toBe('Integrity Constraint Violation');
            expect($details['error_code'])->toBe('DB003');
        });

        it('categorizes different SQLSTATE classes', function () {
            $testCases = [
                ['code' => '08001', 'category' => 'Connection Exception'],
                ['code' => '23505', 'category' => 'Integrity Constraint Violation'],
                ['code' => '40001', 'category' => 'Transaction Rollback'],
                ['code' => '42000', 'category' => 'Syntax Error or Access Violation'],
                ['code' => '22001', 'category' => 'Data Exception'],
            ];

            foreach ($testCases as $testCase) {
                $sqlState = new SqlState($testCase['code']);
                $details = $this->mapper->getTechnicalDetails($sqlState);
                expect($details['category'])->toBe($testCase['category']);
            }
        });
    });

    describe('isRetryable()', function () {
        it('identifies retryable errors', function () {
            $retryableCodes = [
                '40001',  // Deadlock
                '08001',  // Connection failed
                '08003',  // Connection does not exist
                '08006',  // Connection failure during transaction
                '40002',  // Transaction integrity constraint
                '40003',  // Statement completion unknown
            ];

            foreach ($retryableCodes as $code) {
                $sqlState = new SqlState($code);
                expect($this->mapper->isRetryable($sqlState))
                    ->toBeTrue("SQLSTATE {$code} should be retryable");
            }
        });

        it('identifies non-retryable errors', function () {
            $nonRetryableCodes = [
                '23505',  // Unique constraint
                '42000',  // Syntax error
                '22001',  // String data truncation
            ];

            foreach ($nonRetryableCodes as $code) {
                $sqlState = new SqlState($code);
                expect($this->mapper->isRetryable($sqlState))
                    ->toBeFalse("SQLSTATE {$code} should not be retryable");
            }
        });
    });

    describe('getRetryDelay()', function () {
        it('recommends immediate retry for deadlocks', function () {
            $deadlock = new SqlState('40001');
            $delay = $this->mapper->getRetryDelay($deadlock);

            expect($delay)->toBe(1);  // 1 second for quick retry
        });

        it('recommends longer delay for connection errors', function () {
            $connectionError = new SqlState('08001');
            $delay = $this->mapper->getRetryDelay($connectionError);

            expect($delay)->toBe(30);  // 30 seconds for connection recovery
        });

        it('recommends moderate delay for transaction errors', function () {
            $transactionError = new SqlState('40002');
            $delay = $this->mapper->getRetryDelay($transactionError);

            expect($delay)->toBe(5);  // 5 seconds for transaction retry
        });

        it('returns null for non-retryable errors', function () {
            $syntaxError = new SqlState('42000');
            $delay = $this->mapper->getRetryDelay($syntaxError);

            expect($delay)->toBeNull();
        });

        it('provides default delay for retryable errors without specific mapping', function () {
            $unknownRetryable = new SqlState('08999');
            // This should map to CONNECTION_FAILED via class-based fallback

            if ($this->mapper->isRetryable($unknownRetryable)) {
                $delay = $this->mapper->getRetryDelay($unknownRetryable);
                expect($delay)->toBeGreaterThan(0);
            }
        });
    });

    describe('PostgreSQL-specific behavior', function () {
        it('handles PostgreSQL unique violation', function () {
            $pgUniqueViolation = new SqlState('23505');

            expect($this->mapper->mapToErrorCode($pgUniqueViolation))
                ->toBe(DatabaseErrorCode::CONSTRAINT_VIOLATION);

            $message = $this->mapper->getHumanReadableMessage($pgUniqueViolation);
            expect($message)->toContain('Duplicate entry');
        });

        it('handles PostgreSQL table not found', function () {
            $pgTableNotFound = new SqlState('42P01');

            expect($this->mapper->mapToErrorCode($pgTableNotFound))
                ->toBe(DatabaseErrorCode::QUERY_SYNTAX_ERROR);

            $message = $this->mapper->getHumanReadableMessage($pgTableNotFound);
            expect($message)->toContain('table does not exist');
        });
    });

    describe('MySQL-specific behavior', function () {
        it('handles MySQL table not found', function () {
            $mysqlTableNotFound = new SqlState('42S02');

            expect($this->mapper->mapToErrorCode($mysqlTableNotFound))
                ->toBe(DatabaseErrorCode::QUERY_SYNTAX_ERROR);

            $message = $this->mapper->getHumanReadableMessage($mysqlTableNotFound);
            expect($message)->toContain('table not found');
        });

        it('handles MySQL column not found', function () {
            $mysqlColumnNotFound = new SqlState('42S22');

            expect($this->mapper->mapToErrorCode($mysqlColumnNotFound))
                ->toBe(DatabaseErrorCode::QUERY_SYNTAX_ERROR);

            $message = $this->mapper->getHumanReadableMessage($mysqlColumnNotFound);
            expect($message)->toContain('column not found');
        });
    });

    describe('comprehensive error coverage', function () {
        it('covers all major SQLSTATE classes', function () {
            $classes = [
                '08' => 'Connection',
                '23' => 'Constraint',
                '40' => 'Transaction',
                '42' => 'Syntax',
                '22' => 'Data',
            ];

            foreach ($classes as $class => $description) {
                $sqlState = new SqlState($class . '000');
                $errorCode = $this->mapper->mapToErrorCode($sqlState);

                expect($errorCode)->toBeInstanceOf(DatabaseErrorCode::class);
            }
        });

        it('provides meaningful messages for all error types', function () {
            $testCodes = [
                '08001', '23505', '40001', '42000', '22001'
            ];

            foreach ($testCodes as $code) {
                $sqlState = new SqlState($code);
                $message = $this->mapper->getHumanReadableMessage($sqlState);

                expect($message)->not->toBeEmpty();
                expect(strlen($message))->toBeGreaterThan(20);
            }
        });
    });
});
