<?php
/**
 * AEAT Error Classifier
 *
 * Classifies AEAT error codes into categories using pattern matching
 * and a lookup table of known error codes.
 *
 * @package Freefactu
 * @since 1.1.0
 */

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

/**
 * Class Freefactu_Aeat_Error_Classifier
 *
 * Analyzes AEAT error codes and descriptions to determine the error category.
 * Uses both exact code matching and pattern-based detection.
 */
class Freefactu_Aeat_Error_Classifier {

	/**
	 * Known AEAT error codes mapped to categories.
	 * This list is not exhaustive - patterns are used as fallback.
	 *
	 * Codes are based on AEAT VeriFactu technical specifications.
	 *
	 * @var array
	 */
	private static $known_codes = array(
		// Census/NIF errors
		'1100' => Freefactu_Aeat_Error_Categories::CENSUS,    // NIF no encontrado en el censo
		'1101' => Freefactu_Aeat_Error_Categories::CENSUS,    // NIF formato inválido
		'1102' => Freefactu_Aeat_Error_Categories::CENSUS,    // NIF no compatible con tipo de persona
		'1103' => Freefactu_Aeat_Error_Categories::CENSUS,    // Identificador extranjero inválido
		'1104' => Freefactu_Aeat_Error_Categories::CENSUS,    // NIF dado de baja
		'1105' => Freefactu_Aeat_Error_Categories::CENSUS,    // NIF no activado para operaciones
		
		// XML/Format errors
		'2001' => Freefactu_Aeat_Error_Categories::XML_FORMAT, // XML mal formado
		'2002' => Freefactu_Aeat_Error_Categories::XML_FORMAT, // No válido contra XSD
		'2003' => Freefactu_Aeat_Error_Categories::XML_FORMAT, // Campo obligatorio ausente
		'2004' => Freefactu_Aeat_Error_Categories::XML_FORMAT, // Formato de fecha incorrecto
		'2005' => Freefactu_Aeat_Error_Categories::XML_FORMAT, // Caracteres inválidos
		'2006' => Freefactu_Aeat_Error_Categories::XML_FORMAT, // Longitud excedida
		'2007' => Freefactu_Aeat_Error_Categories::HASH_CHAIN, // No debe informarse como primer registro (chaining issue)
		
		// Hash/Chain errors
		'3001' => Freefactu_Aeat_Error_Categories::HASH_CHAIN, // Hash no coincide
		'3002' => Freefactu_Aeat_Error_Categories::HASH_CHAIN, // Hash anterior no coincide
		'3003' => Freefactu_Aeat_Error_Categories::HASH_CHAIN, // Número de factura duplicado
		'3004' => Freefactu_Aeat_Error_Categories::HASH_CHAIN, // Factura fuera de orden en la serie
		'3005' => Freefactu_Aeat_Error_Categories::HASH_CHAIN, // Serie no inicializada
		
		// Rectificativa errors
		'4001' => Freefactu_Aeat_Error_Categories::RECTIFICATIVA, // TipoRectificativa inválido
		'4002' => Freefactu_Aeat_Error_Categories::RECTIFICATIVA, // Falta referencia a factura original
		'4003' => Freefactu_Aeat_Error_Categories::RECTIFICATIVA, // MetodoRectificacion inconsistente
		'4004' => Freefactu_Aeat_Error_Categories::RECTIFICATIVA, // Factura original no existe
		'4005' => Freefactu_Aeat_Error_Categories::RECTIFICATIVA, // Factura original ya rectificada
		
		// Timing errors
		'5001' => Freefactu_Aeat_Error_Categories::TIMING, // Fuera de plazo legal
		'5002' => Freefactu_Aeat_Error_Categories::TIMING, // Fecha inconsistente
		'5003' => Freefactu_Aeat_Error_Categories::TIMING, // Fecha de expedición futura
		
		// Business logic errors
		'6001' => Freefactu_Aeat_Error_Categories::BUSINESS, // Tipo de IVA incorrecto
		'6002' => Freefactu_Aeat_Error_Categories::BUSINESS, // Importes no cuadran
		'6003' => Freefactu_Aeat_Error_Categories::BUSINESS, // Tipo de operación inválido
		'6004' => Freefactu_Aeat_Error_Categories::BUSINESS, // Régimen especial incorrecto
	);

	/**
	 * Pattern-based classification.
	 * Each pattern is matched against error_code + error_description.
	 *
	 * @var array
	 */
	private static $patterns = array(
		Freefactu_Aeat_Error_Categories::CENSUS => array(
			'nif',
			'censo',
			'identificador fiscal',
			'cif',
			'nie',
			'contribuyente',
			'obligado tributario',
			'not found in census',
			'no está dado de alta',
		),
		Freefactu_Aeat_Error_Categories::XML_FORMAT => array(
			'xml',
			'xsd',
			'formato',
			'estructura',
			'schema',
			'validación',
			'elemento',
			'atributo',
			'malformed',
			'well-formed',
		),
		Freefactu_Aeat_Error_Categories::HASH_CHAIN => array(
			'hash',
			'huella',
			'encadenamiento',
			'cadena',
			'duplicad',
			'ya existe',
			'orden',
			'secuencia',
			'anterior',
			'primer registro',
			'existen facturas',
		),
		Freefactu_Aeat_Error_Categories::RECTIFICATIVA => array(
			'rectific',
			'r1',
			'r2',
			'r3',
			'r4',
			'r5',
			'sustitución',
			'diferencias',
			'corregid',
		),
		Freefactu_Aeat_Error_Categories::TIMING => array(
			'plazo',
			'fecha',
			'extemporáneo',
			'fuera de tiempo',
			'deadline',
			'vencido',
			'expir',
		),
		Freefactu_Aeat_Error_Categories::BUSINESS => array(
			'importe',
			'cuota',
			'base imponible',
			'tipo impositivo',
			'iva',
			'operación',
			'régimen',
			'total',
			'no cuadra',
			'inconsisten',
		),
		Freefactu_Aeat_Error_Categories::NETWORK => array(
			'connection',
			'timeout',
			'ssl',
			'tls',
			'network',
			'socket',
			'refused',
			'reset',
			'unavailable',
		),
	);

	/**
	 * Classify an AEAT error.
	 *
	 * @param string $error_code        The AEAT error code.
	 * @param string $error_description The error description text.
	 * @return string The error category constant.
	 */
	public static function classify( $error_code, $error_description = '' ) {
		// 1. Try exact code match first
		$category = self::classify_by_code( $error_code );
		if ( $category !== Freefactu_Aeat_Error_Categories::UNKNOWN ) {
			return $category;
		}

		// 2. Try pattern matching on description
		$category = self::classify_by_pattern( $error_code . ' ' . $error_description );
		if ( $category !== Freefactu_Aeat_Error_Categories::UNKNOWN ) {
			return $category;
		}

		// 3. Allow external classification via filter
		$category = apply_filters(
			'freefactu_error_code_category',
			Freefactu_Aeat_Error_Categories::UNKNOWN,
			$error_code,
			$error_description
		);

		return $category;
	}

	/**
	 * Classify by exact error code match.
	 *
	 * @param string $error_code AEAT error code.
	 * @return string Category or UNKNOWN.
	 */
	private static function classify_by_code( $error_code ) {
		if ( empty( $error_code ) ) {
			return Freefactu_Aeat_Error_Categories::UNKNOWN;
		}

		// Normalize code (remove any prefixes like "AEAT-", etc.)
		$normalized = preg_replace( '/[^0-9]/', '', $error_code );

		if ( isset( self::$known_codes[ $normalized ] ) ) {
			return self::$known_codes[ $normalized ];
		}

		// Also try original code in case it's alphanumeric
		if ( isset( self::$known_codes[ $error_code ] ) ) {
			return self::$known_codes[ $error_code ];
		}

		return Freefactu_Aeat_Error_Categories::UNKNOWN;
	}

	/**
	 * Classify by pattern matching on description.
	 *
	 * @param string $text Combined code and description to analyze.
	 * @return string Category or UNKNOWN.
	 */
	private static function classify_by_pattern( $text ) {
		if ( empty( $text ) ) {
			return Freefactu_Aeat_Error_Categories::UNKNOWN;
		}

		$text_lower = strtolower( $text );

		foreach ( self::$patterns as $category => $patterns ) {
			foreach ( $patterns as $pattern ) {
				if ( strpos( $text_lower, strtolower( $pattern ) ) !== false ) {
					return $category;
				}
			}
		}

		return Freefactu_Aeat_Error_Categories::UNKNOWN;
	}

	/**
	 * Classify an Freefactu_Aeat_Response object and return updated response.
	 *
	 * @param Freefactu_Aeat_Response $response The response to classify.
	 * @return Freefactu_Aeat_Response Response with category set.
	 */
	public static function classify_response( Freefactu_Aeat_Response $response ) {
		if ( $response->is_success() ) {
			// Success responses don't need classification
			return $response;
		}

		$category = self::classify(
			$response->get_error_code(),
			$response->get_error_description()
		);

		return $response->with_category( $category );
	}

	/**
	 * Get all known error codes.
	 * Useful for documentation or admin display.
	 *
	 * @return array Map of code => category.
	 */
	public static function get_known_codes() {
		return self::$known_codes;
	}

	/**
	 * Register a custom error code mapping.
	 * Allows plugins to extend the known codes list.
	 *
	 * @param string $code     Error code.
	 * @param string $category Category constant.
	 */
	public static function register_code( $code, $category ) {
		if ( in_array( $category, Freefactu_Aeat_Error_Categories::get_all(), true ) ) {
			self::$known_codes[ $code ] = $category;
		}
	}
}
