<?php
/**
 * Listing Status Handler
 *
 * Encapsulates business logic for polling listing status and resolving
 * channel details, separating it from the controller.
 *
 * @package    MOMCS_Sync
 * @subpackage MOMCS_Sync/includes/common/handlers
 */

declare(strict_types=1);

namespace MOMCS_Sync\Includes\Common\Handlers;

use MOMCS_Sync\Includes\Common\Services\Amazon\Listings_Service;
use MOMCS_Sync\Includes\Common\Models\Multichannels_Model;
use MOMCS_Sync\Includes\Common\Utils\Logger;
use MOMCS_Sync\Includes\Common\Utils\Variation_Helper;
use MOMCS_Sync\Includes\Common\Utils\Utils;
use MOMCS_Sync\Includes\Common\Utils\Asin_Helper;

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

/**
 * Class Listing_Status_Handler
 */
class Listing_Status_Handler {

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

	/**
	 * Logger instance.
	 *
	 * @var Logger
	 */
	private Logger $logger;

	/**
	 * Multichannels model instance.
	 *
	 * @var Multichannels_Model
	 */
	private Multichannels_Model $multichannels_model;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->listings_service    = new Listings_Service();
		$this->multichannels_model = new Multichannels_Model();
		$this->logger              = new Logger( 'listing-status-handler' );
	}

	/**
	 * Check if a product has actual listing issues (ERROR or WARNING).
	 *
	 * This method properly parses the stored JSON issues meta and checks
	 * if there are actual issues, rather than just checking for meta existence.
	 *
	 * @param int $product_id Product ID.
	 *
	 * @return bool True if the product has actual issues, false otherwise.
	 */
	public static function product_has_issues( int $product_id ): bool {
		$raw = get_post_meta( $product_id, '_momcs_amz_listing_issues', true );
		if ( empty( $raw ) ) {
			return false;
		}
		$decoded = json_decode( $raw, true );
		if ( ! is_array( $decoded ) ) {
			return false;
		}
		return ! empty( $decoded['ERROR'] ) || ! empty( $decoded['WARNING'] );
	}

	/**
	 * Poll SP-API and persist listing status and issues meta for a product.
	 *
	 * @param int    $product_id Product ID.
	 * @param string $seller_id  Seller ID.
	 * @param string $sku        SKU.
	 * @param string $marketplace_id Marketplace ID.
	 *
	 * @return mixed Array on success or WP_Error/null on failure.
	 */
	public function check_status( int $product_id, string $seller_id, string $sku, string $marketplace_id ) {
		$channel_id = get_post_meta( $product_id, '_momcs_amz_channel_id', true );
		if ( ! $channel_id ) {
			$this->logger->error( 'Missing channel ID, cannot poll', array( 'product_id' => $product_id ) );
			return null;
		}

		$response = $this->listings_service->get_listing_status( $channel_id, $seller_id, $sku, $marketplace_id );
		if ( is_wp_error( $response ) ) {
			$this->logger->error(
				'Listing item API error',
				array(
					'product_id' => $product_id,
					'error'      => $response->get_error_message(),
				)
			);
			return $response;
		}

		$summary = isset( $response['summaries'][0] ) ? $response['summaries'][0] : array();
		$status  = strtolower( isset( $summary['status'][0] ) ? $summary['status'][0] : 'processing' );

		switch ( $status ) {
			case 'buyable':
				$meta_status = 'uploaded';
				break;
			case 'discoverable':
				$meta_status = 'discoverable';
				break;
			default:
				$meta_status = 'processing';
		}
		update_post_meta( $product_id, '_momcs_amz_listing_status', $meta_status );

		$issues = isset( $response['issues'] ) ? $response['issues'] : array();
		if ( ! empty( $issues ) ) {
			$grouped = array(
				'ERROR'   => array(),
				'WARNING' => array(),
			);
			foreach ( $issues as $issue ) {
				$severity          = isset( $issue['severity'] ) ? $issue['severity'] : 'WARNING';
				$sev               = strtoupper( $severity );
				$grouped[ $sev ][] = $issue;
			}
			update_post_meta( $product_id, '_momcs_amz_listing_issues', wp_json_encode( $grouped ) );
		} else {
			// Clear stale issues meta when no issues exist.
			delete_post_meta( $product_id, '_momcs_amz_listing_issues' );
		}

		return array(
			'status' => $meta_status,
			'issues' => $issues,
		);
	}

	/**
	 * Resolve latest channel details (marketplace/region and uuid).
	 *
	 * @return array|null Channel details.
	 */
	public function get_channel_details(): ?array {
		$details = $this->multichannels_model->get_latest_channel_details();
		if ( ! $details ) {
			$this->logger->error( 'No channel configuration found in momcs_multichannels table.' );
		}
		return $details;
	}

	/**
	 * Return issues grouped by severity for a product.
	 *
	 * @param int $product_id Product ID.
	 *
	 * @return array<string,mixed> Issues.
	 */
	public function get_issues( int $product_id ): array {
		$raw    = get_post_meta( $product_id, '_momcs_amz_listing_issues', true );
		$issues = array();
		if ( $raw ) {
			$decoded = json_decode( $raw, true );
			if ( is_array( $decoded ) ) {
				$issues = $decoded;
			}
		}
		return $issues;
	}

	/**
	 * Refresh product status for multiple products and return results.
	 *
	 * Supports simple products, variable products (parents), and variations (children).
	 * For variable products, also refreshes status for all variations.
	 *
	 * For Premium users, queries all marketplaces the product is listed on and
	 * returns per-marketplace status data for UI chip rendering.
	 *
	 * @param array<int,int> $product_ids Product IDs.
	 *
	 * @return array<int,array<string,mixed>> Results.
	 *
	 * @throws \RuntimeException When channel configuration not found.
	 * @throws \Exception When checking status fails.
	 */
	public function refresh_status( array $product_ids ): array {
		$channel_details = $this->get_channel_details();
		if ( ! $channel_details ) {
			throw new \RuntimeException( 'Channel configuration not found' );
		}

		$default_marketplace_id = $channel_details['region'];
		$default_channel_id     = $channel_details['uuid'];
		$default_seller_id      = $channel_details['seller_id'];

		$results    = array();
		$is_premium = Utils::is_premium_plan();

		// Expand product IDs to include variations.
		$expanded_product_ids = Variation_Helper::expand_product_ids_with_variations( $product_ids );

		foreach ( $expanded_product_ids as $product_id ) {
			$product = wc_get_product( $product_id );
			if ( ! $product ) {
				continue;
			}

			// Support simple, variable, and variation products.
			$product_type = $product->get_type();
			if ( ! in_array( $product_type, array( 'simple', 'variable', 'variation' ), true ) ) {
				$this->logger->debug(
					'Skipping unsupported product type',
					array(
						'product_id' => $product_id,
						'type'       => $product_type,
					)
				);
				continue;
			}

			// Variable (parent) products don't have their own listing - skip status check but include variations.
			if ( 'variable' === $product_type ) {
				$this->logger->debug( 'Variable product parent - status tracked via variations', array( 'product_id' => $product_id ) );
				continue;
			}

			$sku = $product->get_sku();
			if ( empty( $sku ) ) {
				$this->logger->warning( 'Product missing SKU', array( 'product_id' => $product_id ) );
				continue;
			}

			try {
				if ( $is_premium ) {
					// Premium: Refresh status for ALL marketplaces the product is listed on.
					$this->refresh_premium_product_status(
						(int) $product_id,
						$sku,
						$default_channel_id,
						$default_seller_id,
						$default_marketplace_id
					);

					// Get fresh marketplace data after refresh.
					$marketplace_statuses = Asin_Helper::get_all_marketplace_data( (int) $product_id );
					$listing_status       = get_post_meta( $product_id, '_momcs_amz_listing_status', true );
					$has_issues           = self::product_has_issues( (int) $product_id );

					$results[ $product_id ] = array(
						'success'              => true,
						'listing_status'       => $listing_status,
						'has_issues'           => $has_issues,
						'product_type'         => $product_type,
						'marketplace_statuses' => $marketplace_statuses,
					);

					$this->logger->debug(
						'Premium status refreshed for product',
						array(
							'product_id'   => $product_id,
							'product_type' => $product_type,
							'status'       => $listing_status,
							'marketplaces' => count( $marketplace_statuses ),
						)
					);
				} else {
					// Free/Standard: Existing single-marketplace behavior.
					update_post_meta( $product_id, '_momcs_amz_channel_id', $default_channel_id );
					$status = $this->check_status( (int) $product_id, $default_seller_id, $sku, $default_marketplace_id );
					if ( is_wp_error( $status ) ) {
						throw new \Exception( $status->get_error_message() );
					}
					$listing_status = get_post_meta( $product_id, '_momcs_amz_listing_status', true );
					$has_issues     = self::product_has_issues( $product_id );

					$results[ $product_id ] = array(
						'success'        => true,
						'listing_status' => $listing_status,
						'has_issues'     => $has_issues,
						'product_type'   => $product_type,
					);

					$this->logger->debug(
						'Status refreshed for product',
						array(
							'product_id'   => $product_id,
							'product_type' => $product_type,
							'status'       => $listing_status,
						)
					);
				}
			} catch ( \Exception $e ) {
				$this->logger->error(
					'Error refreshing product status',
					array(
						'product_id' => $product_id,
						'error'      => $e->getMessage(),
					)
				);
				continue;
			}
		}

		return $results;
	}

	/**
	 * Refresh status for a Premium product across all its marketplaces.
	 *
	 * Queries each marketplace the product is listed on and updates
	 * per-marketplace status meta. Also updates legacy status for compatibility.
	 *
	 * @param int    $product_id             Product ID.
	 * @param string $sku                    Product SKU.
	 * @param string $default_channel_id     Default channel UUID.
	 * @param string $default_seller_id      Default seller ID.
	 * @param string $default_marketplace_id Default marketplace ID.
	 *
	 * @return void
	 */
	private function refresh_premium_product_status(
		int $product_id,
		string $sku,
		string $default_channel_id,
		string $default_seller_id,
		string $default_marketplace_id
	): void {
		// Get all marketplace IDs this product is listed on.
		$marketplace_ids = Asin_Helper::get_product_marketplace_ids( $product_id );

		// If no marketplace data exists, use the default marketplace.
		if ( empty( $marketplace_ids ) ) {
			$marketplace_ids = array( $default_marketplace_id );
		}

		$best_status = null;

		foreach ( $marketplace_ids as $mp_id ) {
			// Get channel config for this marketplace (or use default).
			$channel    = $this->get_channel_for_marketplace( $mp_id );
			$seller_id  = $channel['seller_id'] ?? $default_seller_id;
			$channel_id = $channel['uuid'] ?? $default_channel_id;

			// Set channel ID for API calls.
			update_post_meta( $product_id, '_momcs_amz_channel_id', $channel_id );

			// Query SP-API for this marketplace.
			$response = $this->listings_service->get_listing_status( $channel_id, $seller_id, $sku, $mp_id );

			if ( is_wp_error( $response ) ) {
				$this->logger->warning(
					'Failed to refresh status for marketplace',
					array(
						'product_id'     => $product_id,
						'marketplace_id' => $mp_id,
						'error'          => $response->get_error_message(),
					)
				);
				continue;
			}

			// Parse status from response.
			$summary    = isset( $response['summaries'][0] ) ? $response['summaries'][0] : array();
			$api_status = strtolower( isset( $summary['status'][0] ) ? $summary['status'][0] : 'processing' );

			$meta_status = 'processing';
			switch ( $api_status ) {
				case 'buyable':
					$meta_status = 'uploaded';
					break;
				case 'discoverable':
					$meta_status = 'discoverable';
					break;
			}

			// Update per-marketplace status using Asin_Helper.
			Asin_Helper::set_product_status( $product_id, $meta_status, $mp_id );

			// Track best status for legacy meta (prefer uploaded > discoverable > processing).
			if ( null === $best_status ) {
				$best_status = $meta_status;
			} elseif ( 'uploaded' === $meta_status ) {
				$best_status = 'uploaded';
			} elseif ( 'discoverable' === $meta_status && 'uploaded' !== $best_status ) {
				$best_status = 'discoverable';
			}

			$this->logger->debug(
				'Marketplace status updated',
				array(
					'product_id'     => $product_id,
					'marketplace_id' => $mp_id,
					'status'         => $meta_status,
				)
			);
		}

		// Update legacy single-status meta with the "best" status across all marketplaces.
		if ( null !== $best_status ) {
			update_post_meta( $product_id, '_momcs_amz_listing_status', $best_status );
		}
	}

	/**
	 * Get channel configuration for a specific marketplace.
	 *
	 * Attempts to find a channel configured for the given marketplace region.
	 * Returns null if no matching channel is found (fallback to default will be used).
	 *
	 * @param string $marketplace_id Amazon marketplace ID.
	 *
	 * @return array|null Channel details or null if not found.
	 */
	private function get_channel_for_marketplace( string $marketplace_id ): ?array {
		// Try to get channel by marketplace/region.
		$channel = $this->multichannels_model->get_channel_by_region( $marketplace_id );

		if ( $channel ) {
			return $channel;
		}

		// Fallback: return default channel details.
		return $this->get_channel_details();
	}
}
