<?php
/**
 * Amazon Product Sync Service
 *
 * This class orchestrates the product synchronization workflow between
 * WooCommerce and Amazon using the Selling Partner API.
 *
 * @package    MOMCS_Sync
 * @subpackage MOMCS_Sync/includes/common/services/amazon
 * @author     miniOrange
 * @license    Expat
 * @link       https://miniorange.com
 */

declare(strict_types=1);

namespace MOMCS_Sync\Includes\Common\Services\Amazon;

use MOMCS_Sync\Includes\Common\Utils\Logger;
use MOMCS_Sync\Includes\Common\Utils\Variation_Helper;
use MOMCS_Sync\Includes\Common\Exceptions\Sync_Exception;
use MOMCS_Sync\Includes\Common\Exceptions\Amazon_Api_Exception;
use MOMCS_Sync\Includes\Common\Utils\Current_Action;
use MOMCS_Sync\Includes\Common\Handlers\Payload\Payload_Builder;


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

/**
 * Class for handling the Amazon product synchronization workflow.
 */
class Product_Sync_Service {
	/**
	 * Logger instance.
	 *
	 * @var Logger
	 */
	private $logger;

	/**
	 * Token Service instance.
	 *
	 * @var Token_Service
	 */
	private $token_service;

	/**
	 * Catalog Service instance.
	 *
	 * @var Catalog_Service
	 */
	private $catalog_service;

	/**
	 * Restrictions Service instance.
	 *
	 * @var Restrictions_Service
	 */
	private $restrictions_service;

	/**
	 * Listings Service instance.
	 *
	 * @var Listings_Service
	 */
	private $listings_service;

	/**
	 * Product Type Service instance.
	 *
	 * @var Product_Type_Service
	 */
	private $product_type_service;

	/**
	 * Payload Builder instance.
	 *
	 * @var Payload_Builder
	 */
	private $payload_builder;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->token_service        = new Token_Service();
		$this->catalog_service      = new Catalog_Service();
		$this->restrictions_service = new Restrictions_Service();
		$this->listings_service     = new Listings_Service();
		$this->product_type_service = new Product_Type_Service();
		$this->logger               = new Logger( 'product-sync-service' );
		$this->payload_builder      = new Payload_Builder();
	}

	/**
	 * Synchronize a WooCommerce product to Amazon.
	 *
	 * @param int    $product_id     WooCommerce product ID.
	 * @param string $channel_id     Channel UUID.
	 * @param array  $template_config Template configuration.
	 * @param string $marketplace_id Amazon marketplace ID.
	 * @param string $seller_id      Amazon seller ID.
	 * @return array Synchronization result.
	 * @throws \Sync_Exception If the product data cannot be retrieved.
	 * @throws \Amazon_Api_Exception If the product identifier is not found.
	 */
	public function sync_product( $product_id, $channel_id, $template_config, $marketplace_id, $seller_id ) {
		$log_action_id = Current_Action::id();
		$this->logger->debug(
			'Starting product sync',
			array(
				'product_id'     => $product_id,
				'channel_id'     => $channel_id,
				'marketplace_id' => $marketplace_id,
			)
		);

		// Action-Scheduler audit line – build payload separately to keep one argument per line.
		$audit_payload = array(
			'product_title'       => get_the_title( $product_id ),
			'channel_id'          => $channel_id,
			'product_id'          => $product_id,
			'product_sync_status' => 'started',
		);

		$this->log_to_action_scheduler( $log_action_id, 'info', $product_id, $audit_payload, 'product_sync_started' );

		// Step 1: Get product type from template config.
		$product_type = $template_config['selected_category'] ?? '';
		if ( empty( $product_type ) ) {
			$this->logger->error( 'Product type not specified in template config' );
			$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Product type not specified in template config.' );
			throw new Sync_Exception( esc_html( 'Product type not specified in template config for product ID ' . $product_id ) );
		}

		// Get WooCommerce product data.
		$wc_product_data = $this->get_wc_product_data( $product_id );
		if ( empty( $wc_product_data ) ) {
			$this->logger->error(
				'Failed to get WooCommerce product data',
				array(
					'product_id' => $product_id,
				)
			);
			$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Failed to get WooCommerce product data' );
			throw new Sync_Exception( esc_html( 'Failed to get WooCommerce product data for product ID ' . $product_id ) );
		}

		// Step 1: Check if product exists in Amazon catalog.
		$identifier      = $this->get_product_identifier( $wc_product_data );
		$identifier_type = $this->get_product_identifier_type( $wc_product_data );

		if ( empty( $identifier ) ) {
			$this->logger->error(
				'Product identifier not found',
				array(
					'product_id' => $product_id,
				)
			);
			$this->log_to_action_scheduler(
				$log_action_id,
				'error',
				$product_id,
				'Product identifier not found. Please add UPC, EAN, or ISBN to the product.',
				array( 'product_id' => $product_id )
			);
			throw new Sync_Exception( esc_html( 'Product identifier not found. Please add UPC, EAN, or ISBN to the product.' ) );
		}

		$catalog_result = $this->catalog_service->search_catalog_by_identifier(
			$channel_id,
			$identifier,
			$identifier_type,
			$marketplace_id,
			array( 'summaries', 'identifiers' ),
			$log_action_id
		);
		if ( is_wp_error( $catalog_result ) ) {
			$this->logger->error(
				'Error searching catalog',
				array(
					'error' => $catalog_result->get_error_message(),
				)
			);
			$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Error searching catalog: ' . $catalog_result->get_error_message() );
			throw new Amazon_Api_Exception( esc_html( 'Error searching catalog: ' . $catalog_result->get_error_message() ) );
		}

		// Get ASIN if product exists in catalog.
		$asin = '';
		if ( $catalog_result['found'] && ! empty( $catalog_result['items'] ) ) {
			$asin = $catalog_result['items'][0]['asin'] ?? '';
			$this->logger->debug(
				'Product found in catalog',
				array(
					'asin' => $asin,
				)
			);
		}

		if ( ! empty( $asin ) ) {
			// Step 2: Check listing restrictions.
			$restrictions_result = $this->restrictions_service->check_restrictions( $channel_id, $asin, $seller_id, $marketplace_id );
			if ( is_wp_error( $restrictions_result ) ) {
				$this->logger->error(
					'Error checking restrictions',
					array(
						'error' => $restrictions_result->get_error_message(),
					)
				);
				$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Error checking restrictions: ' . $restrictions_result->get_error_message() );
				throw new Amazon_Api_Exception( esc_html( 'Error checking restrictions: ' . $restrictions_result->get_error_message() ) );
			}

			// Handle restrictions.
			if ( $restrictions_result['has_restrictions'] ) {
				$formatted_restrictions = $this->restrictions_service->format_restrictions( $restrictions_result );
				$this->logger->error(
					'Product has listing restrictions',
					array(
						'restrictions' => $formatted_restrictions,
					)
				);

				// Store the restrictions in product meta.
				update_post_meta( $product_id, '_momcs_sync_restrictions', $formatted_restrictions );
				$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Error checking restrictions: ' . $restrictions_result->get_error_message() );

				throw new Sync_Exception( 'Product has listing restrictions: ' . wp_json_encode( $formatted_restrictions ) );
			}
		}

		// Step 4: Build schema-compliant payload using Payload_Builder.
		// Determine product type and use appropriate builder method.
		$wc_product   = wc_get_product( $product_id );
		$is_variable  = Variation_Helper::is_variable_product( $wc_product );
		$is_variation = Variation_Helper::is_variation( $wc_product );

		try {
			if ( $is_variable ) {
				// Variable product (parent) - use build_for_variable.
				$this->logger->debug(
					'Building payload for variable product (parent)',
					array( 'product_id' => $product_id )
				);
				$formatted_data = $this->payload_builder->build_for_variable(
					$product_id,
					$channel_id,
					$product_type,
					$marketplace_id,
					$asin,
					$template_config
				);
			} elseif ( $is_variation ) {
				// Variation (child) - use build_for_variation with parent SKU.
				$parent_sku = Variation_Helper::get_parent_sku( $wc_product );
				$this->logger->debug(
					'Building payload for variation (child)',
					array(
						'product_id' => $product_id,
						'parent_sku' => $parent_sku,
					)
				);
				$formatted_data = $this->payload_builder->build_for_variation(
					$product_id,
					$channel_id,
					$product_type,
					$marketplace_id,
					$parent_sku,
					$asin,
					$template_config
				);
			} else {
				// Simple product - use standard build.
				$formatted_data = $this->payload_builder->build(
					$product_id,
					$channel_id,
					$product_type,
					$marketplace_id,
					$asin,
					$template_config
				);
			}
		} catch ( \RuntimeException $e ) {
			$this->logger->error(
				'Payload builder failed',
				array(
					'error'      => $e->getMessage(),
					'product_id' => $product_id,
				)
			);
			$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Payload generation failed: ' . $e->getMessage() );
			throw new Sync_Exception( esc_html( 'Failed to generate schema-compliant payload: ' . $e->getMessage() ) );
		}

		$this->logger->debug(
            'Payload built successfully',
            array(
                'product_id' => $product_id,
                'payload'    => $formatted_data,
            )
        );
		// Log request payload if action logger id provided.
		if ( $log_action_id ) {
			\ActionScheduler::logger()->log( $log_action_id, '[REQUEST][' . $product_id . '] ' . wp_json_encode( $formatted_data ) );
		}

		// Step 5: Submit listing.
		$sku = $wc_product_data['sku'] ?? '';
		if ( empty( $sku ) ) {
			$this->logger->error( 'Product SKU is required but not found' );
			$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Product SKU is required but not found.' );
			throw new Sync_Exception( esc_html( 'Product SKU is required but not found for product ID ' . $product_id ) );
		}

		$submission_result = $this->listings_service->submit_listing(
			$channel_id,
			$seller_id,
			$sku,
			$formatted_data,
			$marketplace_id,
			'en_US',
			'NORMAL',
			array(
				'log_action_id' => $log_action_id,
				'product_id'    => $product_id,
				'stage'         => 'listing_submit',
			)
		);
		if ( is_wp_error( $submission_result ) ) {
			$this->logger->error(
				'Error submitting listing',
				array(
					'error' => $submission_result->get_error_message(),
				)
			);
			$this->log_to_action_scheduler( $log_action_id, 'error', $product_id, 'Error submitting listing: ' . $submission_result->get_error_message() );
			throw new Amazon_Api_Exception( esc_html( 'Error submitting listing: ' . $submission_result->get_error_message() ) );
		}

		// Check for submission issues.
		if ( 'ACCEPTED' !== $submission_result['status'] ) {
			$this->logger->error(
				'Listing submission failed',
				array(
					'issues' => $submission_result['issues'] ?? array(),
				)
			);
			$grouped = array(
				'ERROR'   => array(),
				'WARNING' => array(),
			);
			foreach ( $submission_result['issues'] as $issue ) {
				$sev               = strtoupper( $issue['severity'] ?? 'WARNING' );
				$grouped[ $sev ][] = $issue;
			}
			// Log an Action Scheduler error row so the logs modal shows a red badge for this product.
			$this->log_to_action_scheduler(
				$log_action_id,
				'error',
				$product_id,
				array(
					'message' => 'Listing submission failed',
					'status'  => $submission_result['status'] ?? '',
					'issues'  => $submission_result['issues'] ?? array(),
				),
				'listing_submit_failed'
			);
			update_post_meta( $product_id, '_momcs_amz_listing_status', 'not uploaded' );
			update_post_meta( $product_id, '_momcs_amz_listing_issues', wp_json_encode( $grouped ) );
			throw new Sync_Exception( esc_html( 'Listing submission failed: ' . wp_json_encode( $submission_result['issues'] ?? array() ) ) );
		}

		// Log response payload if action logger id provided.
		if ( $log_action_id ) {
			\ActionScheduler::logger()->log( $log_action_id, '[RESPONSE][' . $product_id . '] ' . wp_json_encode( $submission_result ) );
		}

		// If ACCEPTED but issues are present, surface a warning entry in logs for visibility.
		if ( ! empty( $submission_result['issues'] ) ) {
			$this->log_to_action_scheduler(
				$log_action_id,
				'warning',
				$product_id,
				array(
					'message' => 'Listing accepted with issues',
					'issues'  => $submission_result['issues'],
				),
				'listing_submit_accepted_with_issues'
			);
		}

		// Step 6: Store mapping in database.
		$this->store_product_mapping( $product_id, $asin );

		// Initialise listing status tracking and schedule polling.
		update_post_meta( $product_id, '_momcs_amz_listing_status', 'processing' );
		update_post_meta( $product_id, '_momcs_amz_channel_id', $channel_id );
		\MOMCS_Sync\Includes\Common\Controllers\Sync\Listing_Status_Controller::schedule( $product_id, $seller_id, $sku, $marketplace_id );

		$this->logger->debug(
			'Product sync completed successfully',
			array(
				'product_id'    => $product_id,
				'submission_id' => $submission_result['submission_id'] ?? '',
			)
		);

		$this->log_to_action_scheduler(
			$log_action_id,
			'success',
			$product_id,
			array(
				'message'             => 'Product synchronized successfully',
				'product_sync_status' => 'completed',
			),
			'product_sync_completed'
		);
		return array(
			'success'       => true,
			'message'       => 'Product synchronized successfully',
			'submission_id' => $submission_result['submission_id'] ?? '',
			'product_id'    => $product_id,
			'asin'          => $asin,
		);
	}

	/**
	 * Get WooCommerce product data.
	 *
	 * @param int $product_id WooCommerce product ID.
	 * @return array Product data.
	 */
	private function get_wc_product_data( $product_id ) {
		$product = wc_get_product( $product_id );
		if ( ! $product ) {
			return array();
		}

		$data = array(
			'id'             => $product->get_id(),
			'name'           => $product->get_name(),
			'sku'            => $product->get_sku(),
			'price'          => $product->get_price(),
			'regular_price'  => $product->get_regular_price(),
			'sale_price'     => $product->get_sale_price(),
			'stock_quantity' => $product->get_stock_quantity(),
			'stock_status'   => $product->get_stock_status(),
			'weight'         => $product->get_weight(),
			'length'         => $product->get_length(),
			'width'          => $product->get_width(),
			'height'         => $product->get_height(),
			'post_title'     => get_the_title( $product_id ),
			'post_content'   => get_post_field( 'post_content', $product_id ),
			'post_excerpt'   => get_post_field( 'post_excerpt', $product_id ),
			'meta_data'      => array(),
		);

		// Get all meta data.
		$meta_data = get_post_meta( $product_id );
		if ( is_array( $meta_data ) ) {
			foreach ( $meta_data as $key => $values ) {
				$data['meta_data'][ $key ] = $values[0] ?? '';
			}
		}

		// Get product attributes.
		$attributes = $product->get_attributes();
		if ( ! empty( $attributes ) ) {
			$data['attributes'] = array();
			foreach ( $attributes as $attribute_name => $attribute ) {
				// For variations, attributes can be strings (attribute values) instead of WC_Product_Attribute objects.
				if ( is_string( $attribute ) ) {
					$data['attributes'][ $attribute_name ] = $attribute;
				} elseif ( $attribute instanceof \WC_Product_Attribute ) {
					if ( $attribute->is_taxonomy() ) {
						$terms                                 = wp_get_post_terms( $product_id, $attribute->get_name(), array( 'fields' => 'names' ) );
						$data['attributes'][ $attribute_name ] = implode( ', ', $terms );
					} else {
						$data['attributes'][ $attribute_name ] = $attribute->get_options();
					}
				} elseif ( is_array( $attribute ) ) {
					// Handle array of options directly.
					$data['attributes'][ $attribute_name ] = implode( ', ', $attribute );
				}
			}
		}

		return $data;
	}

	/**
	 * Get product identifier (UPC, EAN, ISBN, etc.).
	 *
	 * @param array $product_data Product data.
	 * @return string Product identifier.
	 */
	private function get_product_identifier( $product_data ) {
		// Get the stored identifier type and value from product metadata.
		if ( isset( $product_data['meta_data']['_momcs_amz_id_type'] ) && isset( $product_data['meta_data']['_momcs_amz_id_value'] ) ) {
			$id_value = $product_data['meta_data']['_momcs_amz_id_value'];
			if ( ! empty( $id_value ) ) {
				return $id_value;
			}
		}

		// Fallback to checking common meta fields for identifiers.
		$identifier_fields = array( '_upc', '_ean', '_isbn', '_gtin', '_jan', '_global_unique_id' );
		foreach ( $identifier_fields as $field ) {
			if ( isset( $product_data['meta_data'][ $field ] ) && ! empty( $product_data['meta_data'][ $field ] ) ) {
				return $product_data['meta_data'][ $field ];
			}
		}

		// Check for custom attribute.
		if ( isset( $product_data['attributes']['upc'] ) ) {
			return $product_data['attributes']['upc'];
		}
		if ( isset( $product_data['attributes']['ean'] ) ) {
			return $product_data['attributes']['ean'];
		}
		if ( isset( $product_data['attributes']['isbn'] ) ) {
			return $product_data['attributes']['isbn'];
		}

		return '';
	}

	/**
	 * Get product identifier type.
	 *
	 * @param array $product_data Product data.
	 * @return string Identifier type (UPC, EAN, ISBN, etc.).
	 */
	private function get_product_identifier_type( $product_data ) {
		// Get the stored identifier type from product metadata.
		if ( isset( $product_data['meta_data']['_momcs_amz_id_type'] ) ) {
			$id_type = $product_data['meta_data']['_momcs_amz_id_type'];
			if ( ! empty( $id_type ) ) {
				return strtoupper( $id_type );
			}
		}

		// Fallback to checking which identifier is available.
		return 'GTIN'; // Default.
	}

	/**
	 * Store product mapping in database.
	 *
	 * @param int    $product_id WooCommerce product ID.
	 * @param string $asin       Amazon ASIN.
	 * @return void
	 */
	private function store_product_mapping( $product_id, $asin ) {
		// Store ASIN in product meta for easy access.
		if ( ! empty( $asin ) ) {
			update_post_meta( $product_id, '_momcs_asin', $asin );
		}

		// Store sync date in product meta.
		update_post_meta( $product_id, '_momcs_sync_date', current_time( 'mysql' ) );
	}

	/**
	 * Write a structured log entry to the Action Scheduler logger.
	 *
	 * @param int|null     $log_action_id Action Scheduler job id (null when not running within an Action-Scheduler context).
	 * @param string       $severity       Severity level: info|success|warning|error.
	 * @param int          $product_id     WooCommerce product id.
	 * @param string|array $payload        Free-form string or associative array. If array, its keys will be merged into the
	 *                                     structured envelope so the front-end can render a consistent timeline.
	 * @param string       $stage          Optional logical stage (e.g. product_sync_started, catalog_search, etc.).
	 * @return void
	 */
	private function log_to_action_scheduler( $log_action_id, $severity, $product_id, $payload, $stage = '' ) {
		if ( ! $log_action_id ) {
			return;
		}

		// Build structured envelope.
		$entry = array(
			'severity'   => strtolower( $severity ),
			'product_id' => $product_id,
			'stage'      => $stage,
		);

		if ( is_scalar( $payload ) ) {
			$entry['message'] = (string) $payload;
		} elseif ( is_array( $payload ) ) {
			$entry = array_merge( $entry, $payload );
		}

		$json = wp_json_encode( $entry );

		// Keep legacy prefix so older consumers can strip it easily.
		$prefix = '[' . strtoupper( $severity ) . '][' . $product_id . '] ';
		\ActionScheduler::logger()->log( $log_action_id, $prefix . $json );
	}
}
