<?php
/**
 * Jasani App
 *
 * @category   WordPress_Plugin
 * @package    Common
 * @subpackage Suppliers
 * @author     miniOrange <info@miniorange.com>
 * @license    MIT/Expat
 * @link       https://plugins.miniorange.com/woocommerce-api-product-sync-with-woocommerce-rest-apis
 */

namespace MOWCPS\Common\Suppliers;

use WP_Error;
use MOWCPS\Common\Abstracts\Base_Supplier_App;
use MOWCPS\Common\Interfaces\Supplier_App_Interface;
use MOWCPS\Common\Constants\Constants;
use MOWCPS\Common\Constants\Plugin_Filters;
use MOWCPS\Common\Models\Sync_Details_DAO;
use MOWCPS\Common\Services\API\API_Service;
use MOWCPS\Common\Services\Authorization\Authorization_Service;
use MOWCPS\Common\Utils\Utils;
use MOWCPS\Common\Utils\Sync_Utils;

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

/**
 * Class Jasani
 *
 * Handles Jasani supplier app operations (API token in product feed URLs).
 */
class Jasani extends Base_Supplier_App implements Supplier_App_Interface {

	/**
	 * Constructor
	 */
	public function __construct() {
		parent::__construct( Constants::SUPPLIER_APP_TYPE_JASANI );
	}

	/**
	 * Perform Jasani-specific connection test
	 *
	 * @param array $supplier_data Supplier data.
	 * @return array Test result.
	 */
	public function perform_connection_test( $supplier_data ) {
		$validation_result = $this->validate_supplier_specific_configuration( $supplier_data );
		if ( is_wp_error( $validation_result ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $validation_result->get_error_message() ),
			);
		}

		$processed_config = $this->load_and_process_configuration( $supplier_data );
		if ( is_wp_error( $processed_config ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_CONFIG_LOADING_FAILED, $this->supplier_name, $processed_config->get_error_message() ),
			);
		}

		$authorization = $processed_config[ Constants::AUTHORIZATION ] ?? array();

		$auth_service = new Authorization_Service();
		$headers      = $auth_service->generate_headers( $authorization, 0 );

		if ( is_wp_error( $headers ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $headers->get_error_message() ),
			);
		}

		$connections     = $processed_config['connections'] ?? array();
		$test_connection = $this->find_jasani_connection_for_test( $connections );

		if ( empty( $test_connection ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_NO_TEST_ENDPOINT, $this->supplier_name ),
			);
		}

		$config      = $test_connection['configuration'];
		$endpoint    = $config['endpoint'];
		$method      = $config['method'];
		$headers     = array_merge( $headers, $config['headers'] ?? array() );
		$body_config = $config['body'];
		$body_data   = $body_config['data'] ?? array();
		$body        = is_array( $body_data ) ? wp_json_encode( $body_data ) : $body_data;

		if ( empty( $endpoint ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_NO_TEST_ENDPOINT_AVAILABLE, $this->supplier_name ),
			);
		}

		$api_request = array(
			'endpoint' => $endpoint,
			'method'   => $method,
			'headers'  => $headers,
			'body'     => ( Constants::HTTP_REQUEST_METHOD_GET === $method ) ? null : $body,
			'timeout'  => 60,
		);

		$response = API_Service::make_api_call( $api_request['endpoint'], $api_request );

		$response_code    = wp_remote_retrieve_response_code( $response );
		$response_message = wp_remote_retrieve_response_message( $response );
		$raw_body         = wp_remote_retrieve_body( $response );

		if ( is_wp_error( $response ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $response->get_error_message() ),
				$response_code ?? null,
			);
		}

		$response_body = json_decode( $raw_body, true );

		if ( ! Utils::is_status_success( $response ) ) {
			return $this->get_test_result(
				false,
				sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $response_message ?? 'Connection failed' ),
				$response_code,
				$response_body,
			);
		}

		return $this->get_test_result(
			true,
			sprintf( Constants::SUPPLIER_AUTH_SUCCESSFUL, $this->supplier_name ),
			$response_code,
			$response_body,
		);
	}

	/**
	 * Pick a connection for the connection test.
	 *
	 * Uses the stock feed when available (smaller/faster than products/all). Falls back to
	 * priority-1 connection from {@see Base_Supplier_App::find_test_connection()}.
	 *
	 * @param array $connections Connections from processed supplier configuration.
	 * @return array|null
	 */
	private function find_jasani_connection_for_test( $connections ) {
		if ( empty( $connections ) || ! is_array( $connections ) ) {
			return null;
		}

		foreach ( $connections as $connection ) {
			if ( ! is_array( $connection ) ) {
				continue;
			}
			$name = isset( $connection['name'] ) ? strtolower( (string) $connection['name'] ) : '';
			if ( 'stock' === $name ) {
				return $connection;
			}
		}

		return $this->find_test_connection( $connections );
	}

	/**
	 * Validate Jasani-specific configuration
	 *
	 * @param array $supplier_data Supplier data to validate.
	 * @return bool|WP_Error True if valid, WP_Error if invalid.
	 */
	public function validate_supplier_specific_configuration( $supplier_data ) {
		if ( empty( $supplier_data['api_token'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				'API token is required for ' . $this->supplier_name . '.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( ! is_string( $supplier_data['api_token'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				'API token must be a string for ' . $this->supplier_name . '.',
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		return true;
	}

	/**
	 * Stock/price feeds identify rows by Jasani `id` while WC products are keyed by `default_code` in `_sku` meta.
	 *
	 * @param int   $product_id Resolved product ID (0 if not found by primary meta lookup).
	 * @param array $args       supplier_details, connection_details, product_data, etc.
	 * @return int
	 */
	public static function filter_resolve_product_id_after_meta_lookup( $product_id, $args ) {
		if ( ! empty( $product_id ) ) {
			return (int) $product_id;
		}
		if ( ! is_array( $args ) ) {
			return $product_id;
		}

		$supplier = $args['supplier_details'] ?? array();
		if ( ! is_array( $supplier ) || empty( $supplier['app_type'] ) || Constants::SUPPLIER_APP_TYPE_JASANI !== $supplier['app_type'] ) {
			return $product_id;
		}

		$connection = $args['connection_details'] ?? array();
		if ( ! is_array( $connection ) ) {
			return $product_id;
		}

		$connection_name = isset( $connection['name'] ) ? strtolower( (string) $connection['name'] ) : '';
		if ( 'stock' !== $connection_name && 'prices' !== $connection_name ) {
			return $product_id;
		}

		$product_data = $args['product_data'] ?? array();
		if ( is_object( $product_data ) ) {
			$product_data = (array) $product_data;
		}
		if ( ! is_array( $product_data ) ) {
			return $product_id;
		}

		$sku = Sync_Utils::map_particular_attr( 'default_code', $product_data );
		if ( null === $sku || false === $sku ) {
			return $product_id;
		}
		$sku = is_string( $sku ) ? trim( $sku ) : (string) $sku;
		if ( '' === $sku ) {
			return $product_id;
		}

		$found = Sync_Utils::get_product_by_meta( array( '_sku' => $sku ) );

		return (int) $found;
	}

	/**
	 * Entry point for Jasani-specific mowcps_data_alteration_filter handlers.
	 *
	 * @param array $data Filter payload.
	 * @return array
	 */
	public static function apply_jasani_data_alteration_filters( $data ) {
		if ( ! is_array( $data ) ) {
			return $data;
		}

		$data = self::merge_product_gallery_images( $data );
		$data = self::normalize_jasani_variation_attribute_display_names( $data );

		return $data;
	}

	/**
	 * Parent-identifier variable sync: use simple product when only one row and it has no variation attributes.
	 *
	 * @param bool  $use_simple Prior decision from other filters.
	 * @param array $args       Keys: variation_only, parent_context, variance_count, connection_id, supplier_id.
	 * @return bool
	 */
	public static function filter_sync_single_variance_as_simple( $use_simple, $args ) {
		if ( true === $use_simple ) {
			return true;
		}
		if ( ! is_array( $args ) ) {
			return false;
		}
		if ( ! self::is_current_supplier_jasani() ) {
			return false;
		}
		if ( empty( $args['variance_count'] ) || 1 !== (int) $args['variance_count'] ) {
			return false;
		}

		$row = $args['variation_only'] ?? null;

		return self::is_jasani_simple_product_row( $row );
	}

	/**
	 * Jasani API row is a sellable simple product (no template variation attribute values).
	 *
	 * @param mixed $row Product or variation row from API.
	 * @return bool
	 */
	private static function is_jasani_simple_product_row( $row ) {
		if ( empty( $row ) ) {
			return false;
		}
		$row = is_array( $row ) ? $row : (array) $row;

		$attr_ids = $row['product_template_attribute_value_ids'] ?? array();
		if ( is_array( $attr_ids ) && count( $attr_ids ) > 0 ) {
			return false;
		}

		if ( array_key_exists( 'configurable', $row ) && true === $row['configurable'] ) {
			return false;
		}

		return true;
	}

	/**
	 * Whether the active sync supplier is Jasani.
	 *
	 * @return bool
	 */
	private static function is_current_supplier_jasani() {
		$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
		if ( empty( $supplier_id ) ) {
			return false;
		}

		$supplier = Sync_Details_DAO::get_instance()->get_supplier_details( (int) $supplier_id );

		return ! empty( $supplier['app_type'] ) && Constants::SUPPLIER_APP_TYPE_JASANI === $supplier['app_type'];
	}

	/**
	 * Merge Jasani main image_url with nested gallery images for WC product images.
	 *
	 * API returns thumbnail on image_url and extra photos under images[] (array of groups of objects with image_url).
	 * Attribute mapping can keep mapping images -> image_url; this filter expands the list before upload.
	 *
	 * @param array $data Filter payload with event_specifier, images, product_data.
	 * @return array
	 */
	public static function merge_product_gallery_images( $data ) {
		if ( empty( $data['event_specifier'] ) || Plugin_Filters::EVENT_ALTER_PRODUCT_IMAGES !== $data['event_specifier'] ) {
			return $data;
		}

		if ( ! self::is_current_supplier_jasani() ) {
			return $data;
		}

		$product_data = $data['product_data'] ?? array();
		if ( is_object( $product_data ) ) {
			$product_data = json_decode( wp_json_encode( $product_data ), true );
		}
		if ( ! is_array( $product_data ) ) {
			return $data;
		}

		$merged = array();

		if ( ! empty( $product_data['image_url'] ) && is_string( $product_data['image_url'] ) ) {
			$merged[] = $product_data['image_url'];
		}

		foreach ( self::collect_jasani_gallery_image_urls( $product_data['images'] ?? array() ) as $url ) {
			if ( ! is_string( $url ) || '' === $url ) {
				continue;
			}
			if ( ! in_array( $url, $merged, true ) ) {
				$merged[] = $url;
			}
		}

		$data['images'] = $merged;

		return $data;
	}

	/**
	 * Strip "Size: " / "Color: " style prefixes from template attribute display_name values on a product row.
	 *
	 * @param array|\stdClass $product_row API product or variation row.
	 * @return array
	 */
	private static function normalize_jasani_product_row_template_attribute_display_names( $product_row ) {
		if ( is_object( $product_row ) ) {
			$product_row = (array) $product_row;
		}
		if ( ! is_array( $product_row ) ) {
			return array();
		}

		if ( empty( $product_row['product_template_attribute_value_ids'] ) || ! is_array( $product_row['product_template_attribute_value_ids'] ) ) {
			return $product_row;
		}
		$normalized = array();
		foreach ( $product_row['product_template_attribute_value_ids'] as $row ) {
			$item = is_array( $row ) ? $row : (array) $row;
			if ( ! empty( $item['display_name'] ) && is_string( $item['display_name'] ) ) {
				$item['display_name'] = self::strip_jasani_prefixed_attribute_value( $item['display_name'] );
			}
			$normalized[] = $item;
		}
		$product_row['product_template_attribute_value_ids'] = $normalized;
		return $product_row;
	}

	/**
	 * Strip "Size: " / "Color: " style prefixes from variation or parent attribute display_name values.
	 *
	 * Runs on EVENT_ALTER_VARIATION_PRODUCT_DATA (variation row) and EVENT_ALTER_PARENT_PRODUCT_DATA (parent row)
	 * so mapped ##Size## / ##Color## values become e.g. "3X-Large", "White".
	 *
	 * @param array $data Filter payload with event_specifier, variation_array and/or parent_data.
	 * @return array
	 */
	public static function normalize_jasani_variation_attribute_display_names( $data ) {
		if ( ! is_array( $data ) || empty( $data['event_specifier'] ) ) {
			return $data;
		}

		if ( ! self::is_current_supplier_jasani() ) {
			return $data;
		}

		if ( Plugin_Filters::EVENT_ALTER_VARIATION_PRODUCT_DATA === $data['event_specifier'] ) {
			if ( empty( $data['variation_array'] ) ) {
				return $data;
			}

			$variation = $data['variation_array'];
			if ( is_object( $variation ) ) {
				$variation = (array) $variation;
			}
			if ( ! is_array( $variation ) ) {
				return $data;
			}

			$data['variation_array'] = self::normalize_jasani_product_row_template_attribute_display_names( $variation );

			return $data;
		}

		if ( Plugin_Filters::EVENT_ALTER_PARENT_PRODUCT_DATA === $data['event_specifier'] ) {
			if ( empty( $data['parent_data'] ) ) {
				return $data;
			}

			$parent = $data['parent_data'];
			if ( is_object( $parent ) ) {
				$parent = (array) $parent;
			}
			if ( ! is_array( $parent ) ) {
				return $data;
			}

			$data['parent_data'] = self::normalize_jasani_product_row_template_attribute_display_names( $parent );

			return $data;
		}

		return $data;
	}

	/**
	 * Remove "Label: value" prefix from API display_name (prefers ": " then single ":").
	 *
	 * @param string $display_name Raw display_name from Jasani.
	 * @return string
	 */
	private static function strip_jasani_prefixed_attribute_value( $display_name ) {
		$display_name = trim( $display_name );
		$colon_space  = strpos( $display_name, ': ' );
		if ( false !== $colon_space ) {
			return trim( substr( $display_name, $colon_space + 2 ) );
		}

		$colon = strpos( $display_name, ':' );
		if ( false !== $colon ) {
			return trim( substr( $display_name, $colon + 1 ) );
		}

		return $display_name;
	}

	/**
	 * Flatten nested images[][] structures to a list of URL strings.
	 *
	 * @param mixed $images_field Value of product images field from API.
	 * @return array<int, string>
	 */
	private static function collect_jasani_gallery_image_urls( $images_field ) {
		$urls = array();
		if ( empty( $images_field ) || ! is_array( $images_field ) ) {
			return $urls;
		}

		foreach ( $images_field as $node ) {
			if ( is_array( $node ) && isset( $node['image_url'] ) && is_string( $node['image_url'] ) ) {
				$urls[] = $node['image_url'];
				continue;
			}
			if ( is_array( $node ) ) {
				foreach ( $node as $item ) {
					$row = is_array( $item ) ? $item : (array) $item;
					if ( ! empty( $row['image_url'] ) && is_string( $row['image_url'] ) ) {
						$urls[] = $row['image_url'];
					}
				}
			}
		}

		return $urls;
	}
}
