<?php

declare(strict_types=1);

namespace App\Framework\Database\Services;

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

/**
 * SQLSTATE to ErrorCode Mapper
 *
 * Maps 5-character SQLSTATE error codes to framework DatabaseErrorCode enum
 * and provides human-readable error messages for database errors.
 *
 * This service enables conversion of low-level PDO SQLSTATE codes into
 * user-friendly error messages and categorized error codes for better
 * error handling, logging, and recovery strategies.
 */
final readonly class SqlStateErrorMapper
{
    /**
     * Map SQLSTATE code to DatabaseErrorCode enum
     *
     * Converts database-specific SQLSTATE codes into framework error codes
     * with associated metadata (category, severity, recovery hints).
     *
     * @param SqlState $sqlState The SQLSTATE code to map
     * @return DatabaseErrorCode The corresponding framework error code
     */
    public function mapToErrorCode(SqlState $sqlState): DatabaseErrorCode
    {
        return match ($sqlState->code) {
            // Connection errors (Class 08)
            '08001' => DatabaseErrorCode::CONNECTION_FAILED,      // Cannot connect to database
            '08003' => DatabaseErrorCode::CONNECTION_FAILED,      // Connection does not exist
            '08004' => DatabaseErrorCode::CONNECTION_FAILED,      // Server rejected connection
            '08006' => DatabaseErrorCode::CONNECTION_FAILED,      // Connection failure during transaction
            '08007' => DatabaseErrorCode::TRANSACTION_FAILED,     // Transaction resolution unknown

            // Constraint violations (Class 23)
            '23000' => DatabaseErrorCode::CONSTRAINT_VIOLATION,   // Integrity constraint violation
            '23502' => DatabaseErrorCode::CONSTRAINT_VIOLATION,   // Not null violation
            '23503' => DatabaseErrorCode::CONSTRAINT_VIOLATION,   // Foreign key violation
            '23505' => DatabaseErrorCode::CONSTRAINT_VIOLATION,   // Unique violation
            '23514' => DatabaseErrorCode::CONSTRAINT_VIOLATION,   // Check constraint violation

            // Transaction errors (Class 40)
            '40001' => DatabaseErrorCode::DEADLOCK_DETECTED,      // Serialization failure / Deadlock
            '40002' => DatabaseErrorCode::TRANSACTION_FAILED,     // Transaction integrity constraint violation
            '40003' => DatabaseErrorCode::TRANSACTION_FAILED,     // Statement completion unknown

            // Syntax and access errors (Class 42)
            '42000' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Syntax error or access violation
            '42501' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Insufficient privilege
            '42601' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Syntax error
            '42P01' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Undefined table
            '42703' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Undefined column
            '42883' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Undefined function
            '42S02' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Table not found (MySQL)
            '42S22' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,     // Column not found (MySQL)

            // Data exceptions (Class 22)
            '22001' => DatabaseErrorCode::QUERY_EXECUTION_FAILED, // String data right truncation
            '22003' => DatabaseErrorCode::QUERY_EXECUTION_FAILED, // Numeric value out of range
            '22007' => DatabaseErrorCode::QUERY_EXECUTION_FAILED, // Invalid datetime format
            '22012' => DatabaseErrorCode::QUERY_EXECUTION_FAILED, // Division by zero
            '22P02' => DatabaseErrorCode::QUERY_EXECUTION_FAILED, // Invalid text representation

            // Fallback: Use class-based categorization
            default => $this->mapByClass($sqlState),
        };
    }

    /**
     * Get human-readable error message for SQLSTATE code
     *
     * Provides user-friendly error messages based on SQLSTATE code,
     * suitable for displaying to end-users or in API responses.
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return string Human-readable error message
     */
    public function getHumanReadableMessage(SqlState $sqlState): string
    {
        return match ($sqlState->code) {
            // Connection errors (Class 08)
            '08001' => 'Unable to establish database connection. Please check server status and credentials.',
            '08003' => 'Database connection does not exist. Connection may have been lost.',
            '08004' => 'Database server rejected the connection. Please verify credentials and access permissions.',
            '08006' => 'Database connection failed during transaction. Transaction has been rolled back.',
            '08007' => 'Transaction resolution unknown. Database state may be inconsistent.',

            // Constraint violations (Class 23)
            '23000' => 'Data integrity constraint violated. The operation conflicts with existing data rules.',
            '23502' => 'Required field cannot be empty. Please provide a value for all required fields.',
            '23503' => 'Foreign key constraint violation. The referenced record does not exist or cannot be deleted.',
            '23505' => 'Duplicate entry detected. A record with this unique value already exists.',
            '23514' => 'Check constraint violation. The provided value does not meet database validation rules.',

            // Transaction errors (Class 40)
            '40001' => 'Deadlock detected. The operation conflicted with another concurrent transaction. Please retry.',
            '40002' => 'Transaction integrity constraint violation. Transaction has been rolled back.',
            '40003' => 'Transaction completion status unknown. Operation may or may not have completed.',

            // Syntax and access errors (Class 42)
            '42000' => 'Database query syntax error or insufficient access privileges.',
            '42501' => 'Insufficient database privileges to perform this operation.',
            '42601' => 'SQL syntax error. The query contains invalid syntax.',
            '42P01' => 'Database table does not exist. Schema may be outdated or migration pending.',
            '42703' => 'Database column does not exist. Schema may be outdated or migration pending.',
            '42883' => 'Database function does not exist. Feature may not be supported by current database version.',
            '42S02' => 'Database table not found. Please verify database schema.',
            '42S22' => 'Database column not found. Please verify database schema.',

            // Data exceptions (Class 22)
            '22001' => 'Data too long for field. Please reduce the length of the input.',
            '22003' => 'Numeric value out of range. Please provide a valid number within acceptable limits.',
            '22007' => 'Invalid date or time format. Please provide a valid date/time value.',
            '22012' => 'Division by zero error. Invalid calculation attempted.',
            '22P02' => 'Invalid data format. Please provide data in the expected format.',

            // Fallback: Generic message based on class
            default => $this->getGenericMessageByClass($sqlState),
        };
    }

    /**
     * Get technical details for SQLSTATE code
     *
     * Provides technical information about the error for logging and debugging,
     * including SQLSTATE class, subclass, and category information.
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return array<string, string> Technical details array
     */
    public function getTechnicalDetails(SqlState $sqlState): array
    {
        return [
            'sqlstate' => $sqlState->code,
            'class' => $sqlState->getClass(),
            'subclass' => $sqlState->getSubclass(),
            'category' => $this->getCategoryName($sqlState),
            'error_code' => $this->mapToErrorCode($sqlState)->value,
        ];
    }

    /**
     * Check if SQLSTATE represents a retryable error
     *
     * Determines if the error is transient and the operation should be retried.
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return bool True if error is retryable
     */
    public function isRetryable(SqlState $sqlState): bool
    {
        return match ($sqlState->code) {
            '40001' => true,  // Deadlock - always retryable
            '08001', '08003', '08006' => true,  // Connection errors - retryable
            '40002', '40003' => true,  // Transaction failures - retryable
            default => false,
        };
    }

    /**
     * Get recommended retry delay in seconds
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return int|null Recommended delay in seconds, null if not retryable
     */
    public function getRetryDelay(SqlState $sqlState): ?int
    {
        if (!$this->isRetryable($sqlState)) {
            return null;
        }

        return match ($sqlState->code) {
            '40001' => 1,      // Deadlock: retry quickly (1 second)
            '08001', '08003', '08006' => 30,  // Connection: wait longer (30 seconds)
            '40002', '40003' => 5,     // Transaction: moderate delay (5 seconds)
            default => 10,
        };
    }

    /**
     * Map SQLSTATE to ErrorCode by class when specific code not matched
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return DatabaseErrorCode The error code based on SQLSTATE class
     */
    private function mapByClass(SqlState $sqlState): DatabaseErrorCode
    {
        return match ($sqlState->getClass()) {
            '08' => DatabaseErrorCode::CONNECTION_FAILED,
            '23' => DatabaseErrorCode::CONSTRAINT_VIOLATION,
            '40' => DatabaseErrorCode::TRANSACTION_FAILED,
            '42' => DatabaseErrorCode::QUERY_SYNTAX_ERROR,
            '22' => DatabaseErrorCode::QUERY_EXECUTION_FAILED,
            default => DatabaseErrorCode::QUERY_EXECUTION_FAILED,
        };
    }

    /**
     * Get generic message based on SQLSTATE class
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return string Generic error message
     */
    private function getGenericMessageByClass(SqlState $sqlState): string
    {
        return match ($sqlState->getClass()) {
            '08' => 'Database connection error occurred. Please try again later.',
            '23' => 'Data integrity constraint violation. The operation conflicts with database rules.',
            '40' => 'Transaction conflict detected. Please retry the operation.',
            '42' => 'Database query error. The operation contains invalid syntax or insufficient privileges.',
            '22' => 'Invalid data provided. Please verify input format and values.',
            default => "Database error occurred (SQLSTATE: {$sqlState->code}). Please contact support if the issue persists.",
        };
    }

    /**
     * Get category name for SQLSTATE class
     *
     * @param SqlState $sqlState The SQLSTATE code
     * @return string Human-readable category name
     */
    private function getCategoryName(SqlState $sqlState): string
    {
        return match ($sqlState->getClass()) {
            '00' => 'Successful Completion',
            '01' => 'Warning',
            '02' => 'No Data',
            '08' => 'Connection Exception',
            '23' => 'Integrity Constraint Violation',
            '40' => 'Transaction Rollback',
            '42' => 'Syntax Error or Access Violation',
            '22' => 'Data Exception',
            'HY' => 'Driver-Specific Error',
            default => 'Unknown',
        };
    }
}
