<?php
/**
 * Google Merchant Feed Validator
 *
 * Validates product data for Google Merchant Center requirements.
 *
 * @package GGM_Commerce
 * @since 1.0.0
 */

defined( 'ABSPATH' ) || exit;

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter

/**
 * Class GGM_Commerce_Google_Merchant_Validator
 *
 * Validates products and collects errors/warnings for feed generation.
 */
class GGM_Commerce_Google_Merchant_Validator {

	/**
	 * Required fields for Google Merchant.
	 */
	const REQUIRED_FIELDS = array(
		'id',
		'title',
		'description',
		'link',
		'image_link',
		'price',
		'availability',
	);

	/**
	 * Singleton instance.
	 *
	 * @var GGM_Commerce_Google_Merchant_Validator|null
	 */
	private static $instance = null;

	/**
	 * Validation errors by product.
	 *
	 * @var array
	 */
	private $errors = array();

	/**
	 * Validation warnings by product.
	 *
	 * @var array
	 */
	private $warnings = array();

	/**
	 * Products with errors count.
	 *
	 * @var int
	 */
	private $error_count = 0;

	/**
	 * Products with warnings count.
	 *
	 * @var int
	 */
	private $warning_count = 0;

	/**
	 * Get singleton instance.
	 *
	 * @return GGM_Commerce_Google_Merchant_Validator
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Private constructor for singleton.
	 */
	private function __construct() {}

	/**
	 * Reset validation state.
	 */
	public function reset() {
		$this->errors        = array();
		$this->warnings      = array();
		$this->error_count   = 0;
		$this->warning_count = 0;
	}

	/**
	 * Validate a product's mapped data.
	 *
	 * @param int   $product_id  Product ID.
	 * @param array $mapped_data Mapped product data.
	 * @return bool True if valid (no errors), false otherwise.
	 */
	public function validate_product( $product_id, $mapped_data ) {
		$product_errors   = array();
		$product_warnings = array();

		// Check required fields.
		foreach ( self::REQUIRED_FIELDS as $field ) {
			if ( empty( $mapped_data[ $field ] ) ) {
				$product_errors[] = sprintf(
					/* translators: %s: field name */
					__( 'Missing required field: %s', 'ggm-commerce' ),
					$field
				);
			}
		}

		// Validate title length.
		if ( ! empty( $mapped_data['title'] ) ) {
			$title_length = strlen( $mapped_data['title'] );
			if ( $title_length > 150 ) {
				$product_warnings[] = sprintf(
					/* translators: %d: character count */
					__( 'Title exceeds 150 characters (%d). May be truncated by Google.', 'ggm-commerce' ),
					$title_length
				);
			}
			if ( $title_length < 3 ) {
				$product_errors[] = __( 'Title is too short (minimum 3 characters).', 'ggm-commerce' );
			}
		}

		// Validate description length.
		if ( ! empty( $mapped_data['description'] ) ) {
			$desc_length = strlen( $mapped_data['description'] );
			if ( $desc_length > 5000 ) {
				$product_warnings[] = __( 'Description exceeds 5000 characters. Will be truncated.', 'ggm-commerce' );
			}
			if ( $desc_length < 10 ) {
				$product_warnings[] = __( 'Description is very short. Consider adding more detail.', 'ggm-commerce' );
			}
		}

		// Validate price format.
		if ( ! empty( $mapped_data['price'] ) ) {
			if ( ! preg_match( '/^\d+(\.\d{2})?\s[A-Z]{3}$/', $mapped_data['price'] ) ) {
				$product_errors[] = __( 'Invalid price format. Expected: "XX.XX USD"', 'ggm-commerce' );
			}
		}

		// Validate image.
		if ( ! empty( $mapped_data['image_link'] ) ) {
			if ( ! filter_var( $mapped_data['image_link'], FILTER_VALIDATE_URL ) ) {
				$product_errors[] = __( 'Invalid image URL.', 'ggm-commerce' );
			}
		}

		// Validate link.
		if ( ! empty( $mapped_data['link'] ) ) {
			if ( ! filter_var( $mapped_data['link'], FILTER_VALIDATE_URL ) ) {
				$product_errors[] = __( 'Invalid product URL.', 'ggm-commerce' );
			}
		}

		// Check for GTIN or brand+MPN.
		if ( empty( $mapped_data['gtin'] ) ) {
			if ( empty( $mapped_data['brand'] ) || empty( $mapped_data['mpn'] ) ) {
				$product_warnings[] = __( 'No GTIN and missing brand/MPN. Product may have limited visibility.', 'ggm-commerce' );
			}
		} else {
			// Validate GTIN format.
			$gtin = preg_replace( '/\D/', '', $mapped_data['gtin'] );
			if ( ! in_array( strlen( $gtin ), array( 8, 12, 13, 14 ), true ) ) {
				$product_warnings[] = __( 'GTIN length appears incorrect. Expected 8, 12, 13, or 14 digits.', 'ggm-commerce' );
			}
		}

		// Validate availability.
		$valid_availability = array( 'in_stock', 'out_of_stock', 'backorder', 'preorder' );
		if ( ! empty( $mapped_data['availability'] ) && ! in_array( $mapped_data['availability'], $valid_availability, true ) ) {
			$product_errors[] = __( 'Invalid availability value.', 'ggm-commerce' );
		}

		// Validate condition.
		$valid_conditions = array( 'new', 'refurbished', 'used' );
		if ( ! empty( $mapped_data['condition'] ) && ! in_array( $mapped_data['condition'], $valid_conditions, true ) ) {
			$product_errors[] = __( 'Invalid condition value.', 'ggm-commerce' );
		}

		// Store results.
		if ( ! empty( $product_errors ) ) {
			$this->errors[ $product_id ] = $product_errors;
			++$this->error_count;
		}

		if ( ! empty( $product_warnings ) ) {
			$this->warnings[ $product_id ] = $product_warnings;
			++$this->warning_count;
		}

		return empty( $product_errors );
	}

	/**
	 * Validate products before generation (batch validation).
	 *
	 * @param array $products Products to validate.
	 * @return array Validation report.
	 */
	public function validate_products_batch( $products ) {
		global $wpdb;

		$this->reset();

		$report = array(
			'total_products'   => count( $products ),
			'valid_products'   => 0,
			'products_with_errors' => 0,
			'products_with_warnings' => 0,
			'missing_images'   => 0,
			'missing_prices'   => 0,
			'missing_gtin'     => 0,
			'missing_brand'    => 0,
		);

		foreach ( $products as $product ) {
			$has_error = false;

			// Check image.
			if ( empty( $product->image_id ) ) {
				++$report['missing_images'];
				$this->errors[ $product->id ][] = __( 'Missing product image.', 'ggm-commerce' );
				$has_error = true;
			}

			// Check price.
			if ( empty( $product->price ) || $product->price <= 0 ) {
				++$report['missing_prices'];
				$this->errors[ $product->id ][] = __( 'Missing or invalid price.', 'ggm-commerce' );
				$has_error = true;
			}

			// Check GTIN (barcode).
			$has_gtin = $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_product_barcodes
					WHERE product_id = %d AND barcode_type IN ('EAN-13', 'EAN-8', 'UPC-A', 'UPC-E', 'GTIN-14')",
					$product->id
				)
			);
			if ( ! $has_gtin ) {
				++$report['missing_gtin'];
				$this->warnings[ $product->id ][] = __( 'No GTIN barcode assigned.', 'ggm-commerce' );
			}

			// Check brand attribute.
			$settings      = GGM_Commerce_Google_Merchant_Settings::instance();
			$brand_attr_id = $settings->get( 'brand_attribute_id' );
			$default_brand = $settings->get( 'default_brand' );

			if ( $brand_attr_id ) {
				$has_brand = $wpdb->get_var(
					$wpdb->prepare(
						"SELECT COUNT(*) FROM {$wpdb->prefix}ggmc_product_attribute_values
						WHERE product_id = %d AND attribute_id = %d AND value != ''",
						$product->id,
						$brand_attr_id
					)
				);
				if ( ! $has_brand && empty( $default_brand ) ) {
					++$report['missing_brand'];
					$this->warnings[ $product->id ][] = __( 'No brand assigned.', 'ggm-commerce' );
				}
			} elseif ( empty( $default_brand ) ) {
				++$report['missing_brand'];
			}

			if ( $has_error ) {
				++$report['products_with_errors'];
			} else {
				++$report['valid_products'];
			}

			if ( isset( $this->warnings[ $product->id ] ) ) {
				++$report['products_with_warnings'];
			}
		}

		return $report;
	}

	/**
	 * Get all errors.
	 *
	 * @return array
	 */
	public function get_errors() {
		return $this->errors;
	}

	/**
	 * Get errors for a specific product.
	 *
	 * @param int $product_id Product ID.
	 * @return array
	 */
	public function get_product_errors( $product_id ) {
		return $this->errors[ $product_id ] ?? array();
	}

	/**
	 * Get all warnings.
	 *
	 * @return array
	 */
	public function get_warnings() {
		return $this->warnings;
	}

	/**
	 * Get warnings for a specific product.
	 *
	 * @param int $product_id Product ID.
	 * @return array
	 */
	public function get_product_warnings( $product_id ) {
		return $this->warnings[ $product_id ] ?? array();
	}

	/**
	 * Get count of products with errors.
	 *
	 * @return int
	 */
	public function get_error_count() {
		return $this->error_count;
	}

	/**
	 * Get count of products with warnings.
	 *
	 * @return int
	 */
	public function get_warning_count() {
		return $this->warning_count;
	}

	/**
	 * Check if there are any errors.
	 *
	 * @return bool
	 */
	public function has_errors() {
		return $this->error_count > 0;
	}

	/**
	 * Check if there are any warnings.
	 *
	 * @return bool
	 */
	public function has_warnings() {
		return $this->warning_count > 0;
	}

	/**
	 * Get validation summary.
	 *
	 * @return array
	 */
	public function get_summary() {
		return array(
			'error_count'   => $this->error_count,
			'warning_count' => $this->warning_count,
			'errors'        => $this->errors,
			'warnings'      => $this->warnings,
		);
	}

	/**
	 * Get products with errors (for admin display).
	 *
	 * @param int $limit Maximum number of products to return.
	 * @return array
	 */
	public function get_products_with_errors_details( $limit = 50 ) {
		global $wpdb;

		if ( empty( $this->errors ) ) {
			return array();
		}

		$product_ids = array_keys( $this->errors );
		$product_ids = array_slice( $product_ids, 0, $limit );

		if ( empty( $product_ids ) ) {
			return array();
		}

		$placeholders = implode( ',', array_fill( 0, count( $product_ids ), '%d' ) );
		$products     = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, name, sku FROM {$wpdb->prefix}ggmc_products WHERE id IN ($placeholders)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				...$product_ids
			),
			OBJECT_K
		);

		$result = array();
		foreach ( $product_ids as $id ) {
			if ( isset( $products[ $id ] ) ) {
				$result[] = array(
					'id'     => $id,
					'name'   => $products[ $id ]->name,
					'sku'    => $products[ $id ]->sku,
					'errors' => $this->errors[ $id ],
				);
			}
		}

		return $result;
	}

	/**
	 * Get products with warnings (for admin display).
	 *
	 * @param int $limit Maximum number of products to return.
	 * @return array
	 */
	public function get_products_with_warnings_details( $limit = 50 ) {
		global $wpdb;

		if ( empty( $this->warnings ) ) {
			return array();
		}

		$product_ids = array_keys( $this->warnings );
		$product_ids = array_slice( $product_ids, 0, $limit );

		if ( empty( $product_ids ) ) {
			return array();
		}

		$placeholders = implode( ',', array_fill( 0, count( $product_ids ), '%d' ) );
		$products     = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, name, sku FROM {$wpdb->prefix}ggmc_products WHERE id IN ($placeholders)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
				...$product_ids
			),
			OBJECT_K
		);

		$result = array();
		foreach ( $product_ids as $id ) {
			if ( isset( $products[ $id ] ) ) {
				$result[] = array(
					'id'       => $id,
					'name'     => $products[ $id ]->name,
					'sku'      => $products[ $id ]->sku,
					'warnings' => $this->warnings[ $id ],
				);
			}
		}

		return $result;
	}
}
