<?php
/**
 * Client Error Helper for humanizing validation error messages.
 *
 * @package Freefactu
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class Freefactu_Client_Error_Helper
 *
 * Provides utility methods to translate technical Verifactu client library
 * validation error messages into user-friendly Spanish messages.
 */
class Freefactu_Client_Error_Helper {

    /**
     * Error message mappings from technical to human-readable.
     *
     * @var array
     */
    private static $error_mappings = array(
        // NIF/CIF validation errors
        'NIF is required'                           => 'El NIF/CIF del cliente es obligatorio.',
        'Invalid NIF format'                        => 'El formato del NIF/CIF no es válido.',
        'NIF cannot be empty'                       => 'El NIF/CIF del cliente no puede estar vacío.',
        'Invalid Spanish NIF'                       => 'El NIF español introducido no es válido.',
        'Invalid Spanish CIF'                       => 'El CIF introducido no es válido.',
        
        // Recipient errors
        'This type of invoice requires at least one recipient' => 'Este tipo de factura requiere al menos un destinatario con NIF/CIF.',
        'This type of invoice cannot have recipients'          => 'Este tipo de factura (Simplificada/R5) no puede tener destinatarios detallados.',
        'Recipient is required'                     => 'Los datos del destinatario son obligatorios.',
        'Recipient name is required'                => 'El nombre del destinatario es obligatorio.',
        'Recipient country is required'             => 'El país del destinatario es obligatorio.',
        
        // Invoice data errors
        'Invoice number is required'                => 'El número de factura es obligatorio.',
        'Invoice date is required'                  => 'La fecha de la factura es obligatoria.',
        'Invoice total is required'                 => 'El importe total es obligatorio.',
        'Invalid invoice total'                     => 'El importe total de la factura no es válido.',
        'Invalid invoice date'                      => 'La fecha de la factura no es válida.',
        
        // Tax errors
        'Tax rate is required'                      => 'El tipo de IVA es obligatorio.',
        'Invalid tax rate'                          => 'El tipo de IVA no es válido.',
        'Tax base is required'                      => 'La base imponible es obligatoria.',
        'Invalid tax base'                          => 'La base imponible no es válida.',
        
        // Address errors
        'Billing address is required'               => 'La dirección de facturación es obligatoria.',
        'Postal code is required'                   => 'El código postal es obligatorio.',
        'City is required'                          => 'La ciudad es obligatoria.',
        'Country is required'                       => 'El país es obligatorio.',
        
        // Series errors
        'Invoice series is required'                => 'La serie de factura es obligatoria.',
        'Invalid invoice series'                    => 'La serie de factura no es válida.',
        
        // Rectificativa/Correction errors (RegistrationRecord)
        'Original invoice is required for rectificativa'    => 'Se requiere la factura original para crear una rectificativa.',
        'Invalid rectificativa type'                        => 'El tipo de rectificativa no es válido.',
        'Rectificativa reason is required'                  => 'El motivo de la rectificativa es obligatorio.',
        'Record cannot be a prior rejection if it is not a correction' => 'El registro no puede ser un rechazo previo si no es una subsanación.',
        'Missing type for corrective invoice'               => 'Falta el tipo de factura rectificativa.',
        'This type of invoice cannot have a corrective type'=> 'Este tipo de factura no puede tener un tipo rectificativo.',
        'This type of invoice cannot have corrected invoices'=> 'Este tipo de factura no puede tener facturas rectificadas.',
        'Missing corrected base amount for corrective invoice by substitution' => 'Falta la base rectificada para la factura rectificativa por sustitución.',
        'Missing corrected tax amount for corrective invoice by substitution' => 'Falta la cuota rectificada para la factura rectificativa por sustitución.',
        'This invoice cannot have a corrected base amount' => 'Esta factura no puede tener una base rectificada.',
        'This invoice cannot have a corrected tax amount'  => 'Esta factura no puede tener una cuota rectificada.',
        'This type of invoice cannot have replaced invoices' => 'Este tipo de factura no puede tener facturas sustituidas.',
        
        // Chaining errors (Record)
        'Previous hash is required if previous invoice ID is provided' => 'La huella anterior es obligatoria si se proporciona el ID de la factura anterior.',
        'Previous invoice ID is required if previous hash is provided' => 'El ID de la factura anterior es obligatorio si se proporciona la huella anterior.',
        
        // Cancellation errors (CancellationRecord)
        'Previous invoice ID is required for all cancellation records' => 'El ID de la factura anterior es obligatorio para las anulaciones.',
        'Previous hash is required for all cancellation records'       => 'La huella anterior es obligatoria para las anulaciones.',
        
        // Foreign ID errors (ForeignFiscalIdentifier)
        'Type must be passport or unregistered if country code is "ES"' => 'El tipo debe ser pasaporte o no registrado si el país es España.',
        'Country code must be "ES" if type is unregistered'             => 'El código de país debe ser "ES" si el tipo es no registrado.',
        
        // Breakdown/Detail errors (BreakdownDetails)
        'Surcharge rate must be defined for C18 regime type'        => 'El tipo de recargo debe definirse para el régimen C18.',
        'Surcharge amount must be defined for C18 regime type'      => 'La cuota de recargo debe definirse para el régimen C18.',
        'Surcharge rate cannot be defined for non-C18 regime types' => 'El tipo de recargo no puede definirse para regímenes distintos a C18.',
        'Surcharge amount cannot be defined for non-C18 regime types' => 'La cuota de recargo no puede definirse para regímenes distintos a C18.',
        'Tax rate must be defined for subject operation types'      => 'El tipo de IVA debe definirse para operaciones sujetas.',
        'Tax amount must be defined for subject operation types'    => 'La cuota de IVA debe definirse para operaciones sujetas.',
        'Tax rate cannot be defined for non-subject or exempt operation types'   => 'El tipo de IVA no puede definirse para operaciones no sujetas o exentas.',
        'Tax amount cannot be defined for non-subject or exempt operation types' => 'La cuota de IVA no puede definirse para operaciones no sujetas o exentas.',
        'Surcharge rate cannot be defined for non-subject or exempt operation types'   => 'El tipo de recargo no puede definirse para operaciones no sujetas o exentas.',
        'Surcharge amount cannot be defined for non-subject or exempt operation types' => 'La cuota de recargo no puede definirse para operaciones no sujetas o exentas.',
        
        // General validation
        'Validation failed'                         => 'Error de validación de datos.',
        'Required field missing'                    => 'Faltan campos obligatorios.',
        'Invalid data format'                       => 'El formato de los datos no es válido.',
    );

    /**
     * Property path to human-readable field name mappings.
     * Used to translate technical property paths to user-friendly Spanish field names.
     *
     * @var array
     */
    private static $property_mappings = array(
        // Recipients
        'recipients[0].nif'         => 'NIF del destinatario',
        'recipients[0].name'        => 'Nombre del destinatario',
        'recipients[0].country'     => 'País del destinatario',
        'recipients.nif'            => 'NIF del destinatario',
        'recipients.name'           => 'Nombre del destinatario',
        'recipients.country'        => 'País del destinatario',
        'nif'                       => 'NIF/CIF',
        'name'                      => 'Nombre',
        
        // Invoice data
        'invoiceNumber'             => 'Número de factura',
        'invoiceDate'               => 'Fecha de factura',
        'invoiceSeries'             => 'Serie de factura',
        'series'                    => 'Serie',
        'description'               => 'Descripción',
        'total'                     => 'Importe total',
        
        // Chaining
        'previousInvoiceId'         => 'ID de factura anterior',
        'previousHash'              => 'Huella anterior',
        'previousInvoiceDate'       => 'Fecha de factura anterior',
        
        // Breakdown
        'breakdown'                 => 'Desglose de impuestos',
        'breakdown[0].taxBase'      => 'Base imponible',
        'breakdown[0].taxRate'      => 'Tipo de IVA',
        'breakdown[0].taxAmount'    => 'Cuota de IVA',
        'taxBase'                   => 'Base imponible',
        'taxRate'                   => 'Tipo de IVA',
        'taxAmount'                 => 'Cuota de IVA',
        
        // Rectificativa
        'correctiveType'            => 'Tipo de rectificativa',
        'correctedInvoices'         => 'Facturas corregidas',
        'replacedInvoices'          => 'Facturas sustituidas',
        
        // Foreign ID
        'foreignId'                 => 'Identificador extranjero',
        'foreignId.type'            => 'Tipo de identificador extranjero',
        'foreignId.countryCode'     => 'Código de país',
        'foreignId.id'              => 'Número de identificador extranjero',
    );

    /**
     * Symfony Validator constraint messages to Spanish translations.
     * These are the standard messages from Symfony's Validator component.
     *
     * @var array
     */
    private static $symfony_messages = array(
        // Length constraints
        'This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.'
            => 'Este campo debe tener exactamente {{ limit }} caracteres.',
        'This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.'
            => 'Este valor es demasiado corto. Debe tener al menos {{ limit }} caracteres.',
        'This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.'
            => 'Este valor es demasiado largo. Debe tener como máximo {{ limit }} caracteres.',
        
        // NotBlank/NotNull
        'This value should not be blank.'           => 'Este campo no puede estar vacío.',
        'This value should not be null.'            => 'Este campo es obligatorio.',
        
        // Type
        'This value should be of type {{ type }}.'  => 'Este valor debe ser de tipo {{ type }}.',
        'This value is not valid.'                  => 'Este valor no es válido.',
        
        // Range
        'This value should be {{ limit }} or more.' => 'Este valor debe ser {{ limit }} o más.',
        'This value should be {{ limit }} or less.' => 'Este valor debe ser {{ limit }} o menos.',
        'This value should be between {{ min }} and {{ max }}.' => 'Este valor debe estar entre {{ min }} y {{ max }}.',
        
        // Regex/Format
        'This value is not a valid email address.'  => 'Esta dirección de email no es válida.',
        'This value is not a valid URL.'            => 'Esta URL no es válida.',
        'This value is not a valid date.'           => 'Esta fecha no es válida.',
        
        // Choice
        'The value you selected is not a valid choice.' => 'El valor seleccionado no es una opción válida.',
        'One or more of the given values is invalid.'   => 'Uno o más de los valores proporcionados no son válidos.',
        
        // Count
        'This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.'
            => 'Esta colección debe contener al menos {{ limit }} elementos.',
        'This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.'
            => 'Esta colección debe contener como máximo {{ limit }} elementos.',
        'This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.'
            => 'Esta colección debe contener exactamente {{ limit }} elementos.',
    );

    /**
     * Humanize a technical error message into user-friendly Spanish.
     *
     * @param string $error_message The technical error message.
     * @return string User-friendly error message in Spanish.
     */
    public static function humanize( $error_message ) {
        if ( empty( $error_message ) ) {
            return __( 'Error de validación desconocido.', 'freefactu' );
        }

        // Check for exact match first
        if ( isset( self::$error_mappings[ $error_message ] ) ) {
            return self::$error_mappings[ $error_message ];
        }

        // Check for partial matches (case-insensitive)
        $lower_message = strtolower( $error_message );
        
        foreach ( self::$error_mappings as $pattern => $translation ) {
            if ( stripos( $error_message, $pattern ) !== false ) {
                return $translation;
            }
        }

        // Pattern-based matching for common and dynamic error types
        $patterns = array(
            // Specific patterns from simple string matching
            '/nif.*required/i'              => 'El NIF/CIF del cliente es obligatorio.',
            '/nif.*invalid/i'               => 'El NIF/CIF del cliente no es válido.',
            '/cif.*required/i'              => 'El CIF es obligatorio.',
            '/cif.*invalid/i'               => 'El CIF no es válido.',
            '/recipient.*required/i'        => 'Los datos del destinatario son obligatorios.',
            '/invoice.*number.*required/i'  => 'El número de factura es obligatorio.',
            '/invoice.*date.*required/i'    => 'La fecha de la factura es obligatoria.',
            '/address.*required/i'          => 'La dirección es obligatoria.',
            '/postal.*code.*required/i'     => 'El código postal es obligatorio.',
            '/tax.*rate.*invalid/i'         => 'El tipo de IVA no es válido.',
            '/base.*imponible/i'            => 'Error en la base imponible.',
            '/serie.*required/i'            => 'La serie de factura es obligatoria.',
            '/validation.*failed/i'         => 'Error de validación de datos.',
            '/empty.*value/i'               => 'Hay campos vacíos que son obligatorios.',
            '/cannot.*be.*empty/i'          => 'Hay campos vacíos que son obligatorios.',
            '/must.*be.*provided/i'         => 'Faltan datos obligatorios.',
            
            // Dynamic patterns from Verifactu library
            '/Invalid hash, expected value (.*)/' => 'Huella hash incorrecta. Valor esperado: $1',
            '/Expected total tax amount of (.*), got (.*)/' => 'La cuota de  IVA esperada es $1, pero se calculó $2. Revisa los impuestos de las líneas.',
            '/Expected total amount of (.*), got (.*)/' => 'El importe total esperado es $1, pero se calculó $2. Revisa los totales.',
            '/Expected amount of (.*), got (.*)/' => 'El importe calculado ($2) no coincide con el esperado ($1).',
            '/VAT number must start with "(.*)", found "(.*)"/' => 'El número de IVA debe empezar por "$1", encontrado "$2".',
        );

        foreach ( $patterns as $regex => $translation ) {
            if ( preg_match( $regex, $error_message ) ) {
                return $translation;
            }
        }

        // If no translation found, return a generic message with the original error
        // Clean up technical formatting and return
        $cleaned = self::clean_technical_message( $error_message );
        
        return sprintf(
            /* translators: %s: Technical error message */
            __( 'Error de validación: %s', 'freefactu' ),
            $cleaned
        );
    }

    /**
     * Clean up a technical error message for display.
     *
     * @param string $message The technical message.
     * @return string Cleaned message.
     */
    private static function clean_technical_message( $message ) {
        // Handle "Invalid instance of model class" specifically
        // Example: "Invalid instance of model class: - Object(josemmo\Verifactu\Models\Records\RegistrationRecord).recipients[0].nif: This value should have exactly 9 characters. (code 4b6f5c76-22b4-409d-af16-fbe823ba9332)"
        if ( strpos( $message, 'Invalid instance of model class' ) !== false ) {
            return self::parse_symfony_validation_error( $message );
        }
        
        // Remove common technical prefixes
        $prefixes_to_remove = array(
            'Validation error: ',
            'Error: ',
            'Exception: ',
            'InvalidArgumentException: ',
        );
        
        foreach ( $prefixes_to_remove as $prefix ) {
            if ( stripos( $message, $prefix ) === 0 ) {
                $message = substr( $message, strlen( $prefix ) );
            }
        }

        // Capitalize first letter
        $message = ucfirst( trim( $message ) );

        // Add period if missing
        if ( ! empty( $message ) && ! in_array( substr( $message, -1 ), array( '.', '!', '?' ), true ) ) {
            $message .= '.';
        }

        return $message;
    }

    /**
     * Parse a Symfony Validator "Invalid instance of model class" error message.
     *
     * @param string $message The full error message.
     * @return string Parsed and humanized message.
     */
    private static function parse_symfony_validation_error( $message ) {
        // Pattern to extract property path and constraint message
        // Example: "...Object(Class).recipients[0].nif: This value should have exactly 9 characters. (code 4b6f5c76-22b4-409d-af16-fbe823ba9332)"
        $pattern = '/Object\([^)]+\)\.([^:]+):\s*(.+)$/i';
        
        if ( preg_match( $pattern, $message, $matches ) ) {
            $property_path    = trim( $matches[1] );
            $constraint_msg   = trim( $matches[2] );
            
            // Strip the (code UUID) suffix if present
            // Pattern: optional period, optional space, "(code UUID)", to end of string
            $constraint_msg = preg_replace( '/\.?\s*\(code\s+[a-f0-9-]+\)\s*$/i', '', $constraint_msg );
            $constraint_msg = rtrim( $constraint_msg, '. ' );
            
            // Get human-readable field name
            $field_name = self::get_human_field_name( $property_path );
            
            // Translate the constraint message
            $human_constraint = self::translate_constraint_message( $constraint_msg );
            
            // Build final message
            return sprintf( '%s: %s', $field_name, $human_constraint );
        }
        
        // Fallback: try simpler parsing - strip code first from entire message
        $cleaned_message = preg_replace( '/\.?\s*\(code\s+[a-f0-9-]+\)\s*$/i', '', $message );
        $cleaned_message = rtrim( $cleaned_message, '. ' );
        
        $parts = explode( ': ', $cleaned_message );
        if ( count( $parts ) > 2 ) {
            // Get the constraint message (last part)
            $last_part = end( $parts );
            return self::translate_constraint_message( trim( $last_part ) );
        }
        
        return ucfirst( trim( $cleaned_message ) ) . '.';
    }

    /**
     * Get human-readable field name from property path.
     *
     * @param string $property_path The Symfony property path (e.g. "recipients[0].nif").
     * @return string Human-readable field name in Spanish.
     */
    private static function get_human_field_name( $property_path ) {
        // Check for exact match first
        if ( isset( self::$property_mappings[ $property_path ] ) ) {
            return self::$property_mappings[ $property_path ];
        }
        
        // Try without array index (e.g., "recipients[0].nif" -> "recipients.nif")
        $normalized = preg_replace( '/\[\d+\]/', '', $property_path );
        if ( isset( self::$property_mappings[ $normalized ] ) ) {
            return self::$property_mappings[ $normalized ];
        }
        
        // Try just the last property name (e.g., "recipients[0].nif" -> "nif")
        $parts = explode( '.', $property_path );
        $last_prop = preg_replace( '/\[\d+\]/', '', end( $parts ) );
        if ( isset( self::$property_mappings[ $last_prop ] ) ) {
            return self::$property_mappings[ $last_prop ];
        }
        
        // Fallback: convert camelCase to readable format
        $readable = preg_replace( '/([a-z])([A-Z])/', '$1 $2', $last_prop );
        $readable = strtolower( $readable );
        return 'Campo "' . $readable . '"';
    }

    /**
     * Translate a Symfony constraint message to Spanish.
     *
     * @param string $constraint_msg The constraint message from Symfony Validator.
     * @return string Translated message in Spanish.
     */
    private static function translate_constraint_message( $constraint_msg ) {
        // Remove trailing period for matching
        $constraint_msg = rtrim( $constraint_msg, '.' );
        
        // Direct match with Symfony template messages
        foreach ( self::$symfony_messages as $template => $translation ) {
            // Check if the message matches the template pattern
            if ( self::matches_symfony_template( $constraint_msg, $template ) ) {
                // Extract the actual values and substitute
                return self::substitute_symfony_values( $constraint_msg, $template, $translation );
            }
        }
        
        // Pattern-based translations for common constraint messages
        $patterns = array(
            // Length constraints
            '/This value should have exactly (\d+) characters?\.?/i' 
                => 'debe tener exactamente $1 caracteres.',
            '/This value is too short\. It should have (\d+) characters? or more\.?/i'
                => 'es demasiado corto. Debe tener al menos $1 caracteres.',
            '/This value is too long\. It should have (\d+) characters? or less\.?/i'
                => 'es demasiado largo. Debe tener como máximo $1 caracteres.',
            
            // NotBlank/NotNull
            '/This value should not be blank\.?/i'
                => 'no puede estar vacío.',
            '/This value should not be null\.?/i'
                => 'es obligatorio.',
            '/This value is not valid\.?/i'
                => 'no es válido.',
            
            // Range
            '/This value should be (\d+(?:\.\d+)?) or more\.?/i'
                => 'debe ser $1 o más.',
            '/This value should be (\d+(?:\.\d+)?) or less\.?/i'
                => 'debe ser $1 o menos.',
            '/This value should be between ([\d.]+) and ([\d.]+)\.?/i'
                => 'debe estar entre $1 y $2.',
            
            // Type constraints
            '/This value should be of type (\w+)\.?/i'
                => 'debe ser de tipo $1.',
            
            // Choice
            '/The value you selected is not a valid choice\.?/i'
                => 'el valor seleccionado no es válido.',
            
            // Generic
            '/This field is required\.?/i'
                => 'es obligatorio.',
            '/This value is already used\.?/i'
                => 'ya está en uso.',
        );
        
        foreach ( $patterns as $regex => $replacement ) {
            if ( preg_match( $regex, $constraint_msg ) ) {
                return preg_replace( $regex, $replacement, $constraint_msg );
            }
        }
        
        // Fallback: return original with period
        return $constraint_msg . '.';
    }

    /**
     * Check if a message matches a Symfony template pattern.
     *
     * @param string $message  The actual message.
     * @param string $template The Symfony template with {{ placeholders }}.
     * @return bool True if matches.
     */
    private static function matches_symfony_template( $message, $template ) {
        // Handle plural forms (singular|plural)
        if ( strpos( $template, '|' ) !== false ) {
            $parts = explode( '|', $template );
            foreach ( $parts as $part ) {
                if ( self::matches_symfony_template( $message, $part ) ) {
                    return true;
                }
            }
            return false;
        }
        
        // Convert template to regex
        $regex = preg_quote( $template, '/' );
        $regex = preg_replace( '/\\\{\\\{\s*\w+\s*\\\}\\\}/', '(.+)', $regex );
        $regex = '/^' . $regex . '$/i';
        
        return (bool) preg_match( $regex, rtrim( $message, '.' ) );
    }

    /**
     * Substitute values from original message into translated template.
     *
     * @param string $message     Original message with actual values.
     * @param string $template    Source template with {{ placeholders }}.
     * @param string $translation Target translation with {{ placeholders }}.
     * @return string Translated message with values substituted.
     */
    private static function substitute_symfony_values( $message, $template, $translation ) {
        // Handle plural forms
        if ( strpos( $template, '|' ) !== false ) {
            $parts = explode( '|', $template );
            foreach ( $parts as $part ) {
                if ( self::matches_symfony_template( $message, $part ) ) {
                    $template = $part;
                    break;
                }
            }
        }
        if ( strpos( $translation, '|' ) !== false ) {
            // Use the most appropriate form (usually plural for now)
            $trans_parts = explode( '|', $translation );
            $translation = end( $trans_parts );
        }
        
        // Extract placeholder names from template
        preg_match_all( '/\{\{\s*(\w+)\s*\}\}/', $template, $placeholders );
        
        // Convert template to regex to capture values
        $regex = preg_quote( $template, '/' );
        $regex = preg_replace( '/\\\{\\\{\s*\w+\s*\\\}\\\}/', '(.+?)', $regex );
        $regex = '/^' . $regex . '$/i';
        
        if ( preg_match( $regex, rtrim( $message, '.' ), $matches ) ) {
            array_shift( $matches ); // Remove full match
            
            // Substitute values into translation
            $result = $translation;
            foreach ( $placeholders[1] as $index => $name ) {
                if ( isset( $matches[ $index ] ) ) {
                    $result = preg_replace( '/\{\{\s*' . preg_quote( $name, '/' ) . '\s*\}\}/', $matches[ $index ], $result );
                }
            }
            return $result;
        }
        
        return $translation;
    }

    /**
     * Get all available error mappings.
     *
     * @return array Array of error mappings.
     */
    public static function get_mappings() {
        return self::$error_mappings;
    }
}
