<?php
/**
 * Subsanation Validator
 *
 * Validates that subsanation only modifies allowed fields (RF - transmission metadata)
 * and NOT forbidden fields (ROF - core invoice content).
 *
 * @package Freefactu
 * @since 1.1.0
 */

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

/**
 * Class Freefactu_Subsanation_Validator
 *
 * Enforces legal rules for subsanation:
 * - RF fields (Transmission fixes): CAN be changed in subsanation
 * - ROF fields (Core invoice content): CANNOT be changed - requires rectificativa
 *
 * Golden Rule: Subsanation fixes the MESSAGE, not the INVOICE.
 */
class Freefactu_Subsanation_Validator {

	/**
	 * Tolerance for floating-point comparisons (in currency units).
	 * 0.01 = 1 cent tolerance for rounding errors.
	 */
	const AMOUNT_TOLERANCE = 0.01;

	/**
	 * Validate that a subsanation is legal.
	 *
	 * Compares the saved snapshot (what was submitted to AEAT) with current order data.
	 * If any ROF (core content) fields have changed, subsanation is illegal.
	 *
	 * @param array $saved_snapshot Snapshot of original submission.
	 * @param array $current_data   Current order/invoice data.
	 * @return true|WP_Error True if legal, WP_Error with details if illegal.
	 */
	public static function validate( $saved_snapshot, $current_data ) {
		$forbidden_changes = array();
		$change_details = array();

		// ========================================
		// ROF FIELDS - ABSOLUTELY FORBIDDEN TO CHANGE
		// ========================================

		// 1. Total amount
		$saved_total = isset( $saved_snapshot['totals']['total'] ) ? (float) $saved_snapshot['totals']['total'] : 0;
		$current_total = isset( $current_data['totals']['total'] ) ? (float) $current_data['totals']['total'] : 0;
		
		if ( abs( $saved_total - $current_total ) > self::AMOUNT_TOLERANCE ) {
			$forbidden_changes[] = 'total';
			$change_details['total'] = sprintf(
				/* translators: 1: Saved total amount, 2: Current total amount */
				__( 'Total: %1$s → %2$s', 'freefactu' ),
				number_format( $saved_total, 2, ',', '.' ),
				number_format( $current_total, 2, ',', '.' )
			);
		}

		// 2. Subtotal
		$saved_subtotal = isset( $saved_snapshot['totals']['subtotal'] ) ? (float) $saved_snapshot['totals']['subtotal'] : 0;
		$current_subtotal = isset( $current_data['totals']['subtotal'] ) ? (float) $current_data['totals']['subtotal'] : 0;
		
		if ( abs( $saved_subtotal - $current_subtotal ) > self::AMOUNT_TOLERANCE ) {
			$forbidden_changes[] = 'subtotal';
			$change_details['subtotal'] = sprintf(
				/* translators: 1: Saved subtotal amount, 2: Current subtotal amount */
				__( 'Subtotal: %1$s → %2$s', 'freefactu' ),
				number_format( $saved_subtotal, 2, ',', '.' ),
				number_format( $current_subtotal, 2, ',', '.' )
			);
		}

		// 3. Tax total
		$saved_tax = isset( $saved_snapshot['totals']['tax_total'] ) ? (float) $saved_snapshot['totals']['tax_total'] : 0;
		$current_tax = isset( $current_data['totals']['tax_total'] ) ? (float) $current_data['totals']['tax_total'] : 0;
		
		if ( abs( $saved_tax - $current_tax ) > self::AMOUNT_TOLERANCE ) {
			$forbidden_changes[] = 'impuestos';
			$change_details['impuestos'] = sprintf(
				/* translators: 1: Saved tax amount, 2: Current tax amount */
				__( 'Impuestos: %1$s → %2$s', 'freefactu' ),
				number_format( $saved_tax, 2, ',', '.' ),
				number_format( $current_tax, 2, ',', '.' )
			);
		}

		// 4. Items count (adding/removing items is forbidden)
		$saved_items_count = isset( $saved_snapshot['items'] ) ? count( $saved_snapshot['items'] ) : 0;
		$current_items_count = isset( $current_data['items'] ) ? count( $current_data['items'] ) : 0;
		
		if ( $saved_items_count !== $current_items_count ) {
			$forbidden_changes[] = 'líneas';
			$change_details['líneas'] = sprintf(
				/* translators: 1: Saved lines count, 2: Current lines count */
				__( 'Líneas: %1$d → %2$d', 'freefactu' ),
				$saved_items_count,
				$current_items_count
			);
		}

		// 5. Tax breakdown structure
		$saved_tax_count = isset( $saved_snapshot['tax_breakdown'] ) ? count( $saved_snapshot['tax_breakdown'] ) : 0;
		$current_tax_count = isset( $current_data['tax_breakdown'] ) ? count( $current_data['tax_breakdown'] ) : 0;
		
		if ( $saved_tax_count !== $current_tax_count ) {
			$forbidden_changes[] = 'desglose IVA';
			$change_details['desglose IVA'] = sprintf(
				/* translators: 1: Saved tax breakdown count, 2: Current tax breakdown count */
				__( 'Tipos de IVA: %1$d → %2$d', 'freefactu' ),
				$saved_tax_count,
				$current_tax_count
			);
		}

		// 6. Item-level changes (if counts match, check individual items)
		if ( $saved_items_count === $current_items_count && $saved_items_count > 0 ) {
			$item_changes = self::compare_items( $saved_snapshot['items'], $current_data['items'] );
			if ( ! empty( $item_changes ) ) {
				$forbidden_changes[] = 'contenido de líneas';
				$change_details['contenido de líneas'] = implode( '; ', $item_changes );
			}
		}

		// 7. Tax breakdown amounts
		if ( $saved_tax_count === $current_tax_count && $saved_tax_count > 0 ) {
			$tax_changes = self::compare_tax_breakdown( $saved_snapshot['tax_breakdown'], $current_data['tax_breakdown'] );
			if ( ! empty( $tax_changes ) ) {
				$forbidden_changes[] = 'importes IVA';
				$change_details['importes IVA'] = implode( '; ', $tax_changes );
			}
		}

		// If any forbidden changes found, return error
		if ( ! empty( $forbidden_changes ) ) {
			$error_message = sprintf(
				/* translators: %s: List of changed fields */
				__( 'No se permite subsanar: los siguientes campos económicos han cambiado: %s. La subsanación solo corrige errores de transmisión, no de contenido. Debe crear una factura rectificativa.', 'freefactu' ),
				implode( ', ', $forbidden_changes )
			);

			return new WP_Error(
				'illegal_subsanation',
				$error_message,
				array(
					'forbidden_changes' => $forbidden_changes,
					'change_details'    => $change_details,
				)
			);
		}

		return true;
	}

	/**
	 * Compare individual line items.
	 *
	 * @param array $saved_items   Items from saved snapshot.
	 * @param array $current_items Current items.
	 * @return array List of change descriptions.
	 */
	private static function compare_items( $saved_items, $current_items ) {
		$changes = array();

		foreach ( $saved_items as $index => $saved_item ) {
			if ( ! isset( $current_items[ $index ] ) ) {
				continue; // Count mismatch already handled
			}

			$current_item = $current_items[ $index ];

			// Check quantity
			$saved_qty = isset( $saved_item['quantity'] ) ? (float) $saved_item['quantity'] : 0;
			$current_qty = isset( $current_item['quantity'] ) ? (float) $current_item['quantity'] : 0;
			if ( abs( $saved_qty - $current_qty ) > 0.0001 ) {
				$changes[] = sprintf(
					/* translators: 1: Line number, 2: Saved quantity, 3: Current quantity */
					__( 'Cantidad línea %1$d: %2$s → %3$s', 'freefactu' ),
					$index + 1,
					$saved_qty,
					$current_qty
				);
			}

			// Check unit price
			$saved_price = isset( $saved_item['unit_price'] ) ? (float) $saved_item['unit_price'] : 0;
			$current_price = isset( $current_item['unit_price'] ) ? (float) $current_item['unit_price'] : 0;
			if ( abs( $saved_price - $current_price ) > self::AMOUNT_TOLERANCE ) {
				$changes[] = sprintf(
					/* translators: 1: Line number, 2: Saved price, 3: Current price */
					__( 'Precio línea %1$d: %2$s → %3$s', 'freefactu' ),
					$index + 1,
					number_format( $saved_price, 2, ',', '.' ),
					number_format( $current_price, 2, ',', '.' )
				);
			}

			// Check total
			$saved_line_total = isset( $saved_item['total'] ) ? (float) $saved_item['total'] : 0;
			$current_line_total = isset( $current_item['total'] ) ? (float) $current_item['total'] : 0;
			if ( abs( $saved_line_total - $current_line_total ) > self::AMOUNT_TOLERANCE ) {
				$changes[] = sprintf(
					/* translators: 1: Line number, 2: Saved line total, 3: Current line total */
					__( 'Total línea %1$d: %2$s → %3$s', 'freefactu' ),
					$index + 1,
					number_format( $saved_line_total, 2, ',', '.' ),
					number_format( $current_line_total, 2, ',', '.' )
				);
			}
		}

		return $changes;
	}

	/**
	 * Compare tax breakdown.
	 *
	 * @param array $saved_breakdown   Tax breakdown from saved snapshot.
	 * @param array $current_breakdown Current tax breakdown.
	 * @return array List of change descriptions.
	 */
	private static function compare_tax_breakdown( $saved_breakdown, $current_breakdown ) {
		$changes = array();

		foreach ( $saved_breakdown as $index => $saved_tax ) {
			if ( ! isset( $current_breakdown[ $index ] ) ) {
				continue;
			}

			$current_tax = $current_breakdown[ $index ];

			// Check rate
			$saved_rate = isset( $saved_tax['rate'] ) ? (float) $saved_tax['rate'] : 0;
			$current_rate = isset( $current_tax['rate'] ) ? (float) $current_tax['rate'] : 0;
			if ( abs( $saved_rate - $current_rate ) > 0.0001 ) {
				$changes[] = sprintf(
					/* translators: 1: Saved tax rate, 2: Current tax rate */
					__( 'Tipo IVA: %1$s%% → %2$s%%', 'freefactu' ),
					$saved_rate,
					$current_rate
				);
			}

			// Check base
			$saved_base = isset( $saved_tax['base'] ) ? (float) $saved_tax['base'] : 0;
			$current_base = isset( $current_tax['base'] ) ? (float) $current_tax['base'] : 0;
			if ( abs( $saved_base - $current_base ) > self::AMOUNT_TOLERANCE ) {
				$changes[] = sprintf(
					/* translators: 1: Tax rate, 2: Saved base amount, 3: Current base amount */
					__( 'Base IVA %1$s%%: %2$s → %3$s', 'freefactu' ),
					$saved_rate,
					number_format( $saved_base, 2, ',', '.' ),
					number_format( $current_base, 2, ',', '.' )
				);
			}

			// Check amount
			$saved_amount = isset( $saved_tax['amount'] ) ? (float) $saved_tax['amount'] : 0;
			$current_amount = isset( $current_tax['amount'] ) ? (float) $current_tax['amount'] : 0;
			if ( abs( $saved_amount - $current_amount ) > self::AMOUNT_TOLERANCE ) {
				$changes[] = sprintf(
					/* translators: 1: Tax rate, 2: Saved amount, 3: Current amount */
					__( 'Cuota IVA %1$s%%: %2$s → %3$s', 'freefactu' ),
					$saved_rate,
					number_format( $saved_amount, 2, ',', '.' ),
					number_format( $current_amount, 2, ',', '.' )
				);
			}
		}

		return $changes;
	}

	/**
	 * Get a summary of what changed for logging/debugging.
	 *
	 * @param array $saved_snapshot Snapshot of original submission.
	 * @param array $current_data   Current order/invoice data.
	 * @return array Summary of changes (allowed and forbidden).
	 */
	public static function get_change_summary( $saved_snapshot, $current_data ) {
		$summary = array(
			'allowed_changes'   => array(),
			'forbidden_changes' => array(),
		);

		// Check customer NIF (allowed to fix typos)
		$saved_nif = isset( $saved_snapshot['customer']['nif'] ) ? $saved_snapshot['customer']['nif'] : '';
		$current_nif = isset( $current_data['customer']['nif'] ) ? $current_data['customer']['nif'] : '';
		if ( $saved_nif !== $current_nif ) {
			$summary['allowed_changes'][] = sprintf(
				/* translators: 1: Saved NIF, 2: Current NIF */
				__( 'NIF cliente: %1$s → %2$s', 'freefactu' ),
				$saved_nif ?: '(vacío)',
				$current_nif ?: '(vacío)'
			);
		}

		// Check customer name (allowed minor corrections)
		$saved_name = isset( $saved_snapshot['customer']['name'] ) ? $saved_snapshot['customer']['name'] : '';
		$current_name = isset( $current_data['customer']['name'] ) ? $current_data['customer']['name'] : '';
		if ( $saved_name !== $current_name ) {
			$summary['allowed_changes'][] = sprintf(
				/* translators: 1: Saved name, 2: Current name */
				__( 'Nombre cliente: %1$s → %2$s', 'freefactu' ),
				$saved_name ?: '(vacío)',
				$current_name ?: '(vacío)'
			);
		}

		// Run validation to get forbidden changes
		$validation = self::validate( $saved_snapshot, $current_data );
		if ( is_wp_error( $validation ) ) {
			$error_data = $validation->get_error_data();
			if ( isset( $error_data['change_details'] ) ) {
				$summary['forbidden_changes'] = $error_data['change_details'];
			}
		}

		return $summary;
	}
}
