<?php
/**
 * SNS Activewear 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\Services\API\API_Service;
use MOWCPS\Common\Services\Authorization\Authorization_Service;
use MOWCPS\Common\Utils\Configuration_Loader;
use MOWCPS\Common\Constants\Plugin_Filters;
use MOWCPS\Common\Models\Connection_Model;
use MOWCPS\Common\Services\Logging\Supplier_Logger;
use MOWCPS\Common\Models\Sync_Details_DAO;
use MOWCPS\Common\Services\Filter_Data_Services;
use MOWCPS\Common\Utils\Utils;
use MOWCPS\Common\Utils\Model_Utils;
use MOWCPS\Common\Utils\Sync_Utils;
use MOWCPS\Common\Models\Preview_Filters_Model;
use MOWCPS\Common\Models\Setting_Model;
use MOWCPS\Common\Services\Preview\Preview_Filter_Service;
use MOWCPS\Common\Models\Authorization_Model;
use MOWCPS\Common\Models\Supplier_Model;
use MOWCPS\Common\Services\Cache\Cache_Service;

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

/**
 * Class Sns_Activewear
 *
 * Handles SNS Activewear supplier app operations
 */
class Sns_Activewear extends Base_Supplier_App implements Supplier_App_Interface {

	/** Transient key prefix for categories/brands response (suffixed by country, e.g. US, CA). Cleared when that supplier is updated/deleted. */
	public const CACHE_KEY_PREFIX_CATEGORIES_BRANDS = 'mowcps_sns_activewear_categories_brands_';

	private const CACHE_FILE_PATH = MOWCPS_PLUGIN_DIR . 'cache' . DIRECTORY_SEPARATOR . 'sns_activewear_styles_cache.json';
	/**
	 * Constructor
	 */
	public function __construct() {
		parent::__construct( Constants::SUPPLIER_APP_TYPE_SNS_ACTIVEWEAR );
	}

	/**
	 * Perform SNS Activewear-specific connection test
	 *
	 * @param array $supplier_data Supplier data.
	 * @return array Test result.
	 */
	public function perform_connection_test( $supplier_data ) {
		// Validate supplier-specific configuration using existing validation method.
		$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() ),
			);
		}

		// Load and process configuration with dynamic parameters.
		$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() ),
			);
		}

		$country  = $supplier_data['country'] ?? null;
		$response = $this->fetch_brands_and_categories( $processed_config, $country );

		$response_code    = wp_remote_retrieve_response_code( $response );
		$response_message = wp_remote_retrieve_response_message( $response );
		$response_body    = json_decode( wp_remote_retrieve_body( $response ), true );

		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,
			);
		}

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

		// Extract unique base categories from the response.
		$base_categories = $this->extract_categories_and_brands( $response_body );

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

	/**
	 * Validate SNS Activewear-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 ) {
		// Validate required fields for SNS Activewear.
		if ( empty( $supplier_data['country'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_MISSING_COUNTRY, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( empty( $supplier_data['account_number'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_MISSING_ACCOUNT_NUMBER, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		if ( empty( $supplier_data['api_key'] ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_MISSING_API_KEY, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		// Validate country format (should be a valid country code).
		$valid_countries = array( Constants::COUNTRY_US, Constants::COUNTRY_CA );
		if ( ! in_array( $supplier_data['country'], $valid_countries, true ) ) {
			return new WP_Error(
				Constants::ERROR_INVALID_SUPPLIER_CONFIG,
				sprintf( Constants::SUPPLIER_INVALID_COUNTRY, $this->supplier_name ),
				array( 'status' => Constants::HTTP_BAD_REQUEST )
			);
		}

		return true;
	}

	/**
	 * Cache API response
	 *
	 * @param array $data Filter data.
	 * @return array Modified filter data.
	 */
	public static function handle_api_response_cache( $data ) {
		if ( Plugin_Filters::EVENT_ALTER_PRE_API_CALL_DATA === $data['event_specifier'] ) {
			$data = self::batch_style_ids( $data );
		}
		return $data;
	}

	/**
	 * Enhance product name and description with additional API call
	 *
	 * @param array $data Filter data.
	 * @return array Modified filter data.
	 */
	public static function fetch_name_description( $data ) {
		if ( false === strpos( Constants::MOWCPS_PLAN_NAME, 'standard' ) ) {
			return $data;
		}
		// Check if this is a name or description request.
		if ( ! isset( $data['event_specifier'] ) || Plugin_Filters::EVENT_UPDATE_PRODUCT_DATA !== $data['event_specifier'] ) {
			return $data;
		}

		// Check if we have styleID in product data.
		if ( ! isset( $data['product_data']['styleID'] ) || empty( $data['product_data']['styleID'] ) ) {
			return $data;
		}

		$style_id    = $data['product_data']['styleID'];
		$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();

		// Get authorization config.
		$authorization_config = Sync_Details_DAO::get_instance()->get_authorization_details( $supplier_id );

		// Get styles connection for the additional API call.
		$styles_connection = Sync_Details_DAO::get_instance()->get_connection_details(
			Connection_Model::get_connection_id_by_name( $supplier_id, 'Styles' )
		);

		if ( ! $styles_connection ) {
			return $data;
		}

		$styles_config = $styles_connection['configuration'];

		if ( $styles_connection['configuration']['auth_enabled'] ) {
			// Prepare API request for individual style.
			$auth_service = new Authorization_Service();
			$headers      = $auth_service->generate_headers( $authorization_config, $supplier_id );
		} else {
			$headers = array();
		}

		$headers = array_merge( $headers, $styles_config['headers'] );
		$body    = $styles_config['body'];

		// Replace styleID placeholder with single styleID for this product.
		$endpoint = str_replace( '{{styleID}}', $style_id, $styles_config['endpoint'] );
		// Check cache first based on style_id.
		$cache_key   = 'sns_style_data_' . $supplier_id . '_' . $style_id;
		$cached_data = get_transient( $cache_key );

		if ( false !== $cached_data ) {
			// Cache hit - use cached data.
			$data['product_data'] = $cached_data;
			return $data;
		}
		$api_request = array(
			'endpoint' => $endpoint,
			'method'   => $styles_config['method'],
			'headers'  => $headers,
			'body'     => $body,
		);

		// Make API call.
		$api_response  = API_Service::make_api_call( $api_request['endpoint'], $api_request );
		$response_body = wp_remote_retrieve_body( $api_response );

		// Handle JSON responses.
		$response = json_decode( $response_body, true );

		// Store in cache if response is valid.
		if ( ! empty( $response ) && ! is_wp_error( $api_response ) ) {
			// Get the last cached style_id for this supplier.
			$last_cached_style_option = 'sns_last_cached_style_' . $supplier_id;
			$last_cached_style_id     = get_option( $last_cached_style_option );

			if ( $last_cached_style_id && $last_cached_style_id !== $style_id ) {
				$old_cache_key = 'sns_style_data_' . $supplier_id . '_' . $last_cached_style_id;
				delete_transient( $old_cache_key );
			}

			set_transient( $cache_key, $response, DAY_IN_SECONDS );
		}
		$data['product_data'] = $response;
		return $data;
	}

	/**
	 * Store outbound specification data
	 *
	 * @param array $data Filter data.
	 * @return array Modified filter data.
	 */
	public static function store_outbound_specification_data( $data ) {
		if ( Plugin_Filters::EVENT_OUTBOUND_DATA_ALTERATION === $data['event_specifier'] ) {
			Model_Utils::update_option( 'mowcps_sns_specification_data', $data['products_array'] );
		}
		return $data;
	}

	/**
	 * Generate specification table
	 *
	 * @param array $data Filter data.
	 * @return array Modified filter data.
	 */
	public static function generate_specification_table( $data ) {
		if ( ! empty( $data['event_specifier'] ) ) {
			$event = $data['event_specifier'];
		}

		if ( ( Plugin_Filters::EVENT_SET_CUSTOM_FIELDS === $event ) && isset( $data['post_id'] ) ) {
				$specification_data = Model_Utils::get_option( 'mowcps_sns_specification_data', array() );

			if ( ! empty( $data['custom_fields']['mo_specification'] ) ) {

				// API response will already be set in the data.
				$specification = $data['custom_fields']['mo_specification'];

				if ( ! empty( $specification_data ) ) {
					$spec_names = array();
					$size_names = array();

					foreach ( $specification_data as $item ) {
						$spec_names[ $item['specName'] ] = $item['specName'];
						$size_names[ $item['sizeName'] ] = $item['sizeName'];
					}
					ksort( $size_names );

					// Process the data (example: generating a table).
					$table_html  = '<table border="1">';
					$table_html .= '<tr>';
					$table_html .= '<th>  </th>';
					foreach ( $size_names as $size_name ) {
						$table_html .= '<th>' . esc_html( $size_name ) . '</th>';
					}
					$table_html .= '</tr>';

					foreach ( $spec_names as $spec_name ) {
						$table_html .= '<tr>';
						$table_html .= '<td>' . esc_html( $spec_name ) . '</td>';

						// Add values for each sizeName.
						foreach ( $size_names as $size_name ) {
							// Find the value for the current specName and sizeName.
							$value = '';
							foreach ( $specification_data as $item ) {
								if ( $item['specName'] === $spec_name && $item['sizeName'] === $size_name ) {
									$value = $item['value'];
									break;
								}
							}
							$table_html .= '<td>' . esc_html( $value ) . '</td>';
						}
						$table_html .= '</tr>';
					}
					$table_html .= '</table>';
					update_post_meta( $data['post_id'], $specification, $table_html );
				}
			}
		}
		return $data;
	}

	/**
	 * Get sub categories
	 *
	 * @param array $data Filter data.
	 * @return array Modified filter data.
	 */
	public static function get_sub_categories( $data ) {
		if ( Plugin_Filters::EVENT_ALTER_PRODUCTS_CATEGORY === $data['event_specifier'] ) {
			$supplier_id          = Sync_Details_DAO::get_instance()->get_supplier_id();
			$authorization_config = Sync_Details_DAO::get_instance()->get_authorization_details( $supplier_id );

			$details = Sync_Details_DAO::get_instance()->get_supplier_details( $supplier_id )['details'];

			$categories_config = Configuration_Loader::load_supplier_config( Constants::SUPPLIER_APP_TYPE_SNS_ACTIVEWEAR );

			$categories_config = Configuration_Loader::process_configuration( $categories_config, json_decode( $details, true ) );
			$categories_config = $categories_config[ Constants::CONNECTIONS ][0]['configuration'];

			if ( $categories_config['auth_enabled'] ) {
				$auth_service = new Authorization_Service();
				$headers      = $auth_service->generate_headers( $authorization_config, $supplier_id );
			} else {
				$headers = array();
			}

			$parent_category    = $data['parent_category'];
			$sub_categories     = $data['categories'][ $parent_category ][1];
			$sub_category_array = array();

			if ( isset( $sub_categories ) && ! empty( $sub_categories ) ) {
				$category_ids = $sub_categories;
				$url          = $categories_config['endpoint'] . '/' . $category_ids;

				$api_request = array(
					'url'     => $url,
					'method'  => $categories_config['method'],
					'headers' => $headers,
					'body'    => $categories_config['body'],
				);

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

				if ( is_wp_error( $response ) ) {
					return $data;
				}

				$response = wp_remote_retrieve_body( $response );
				$response = json_decode( $response, true );

				if ( is_array( $response ) ) {
					foreach ( $response as $category ) {
						$sub_category_array[] = $category['name'];
					}
				}
			}

			$data['categories'][ $parent_category ] = $sub_category_array;
		}
		return $data;
	}

	/**
	 * Batch style IDs for products API calls
	 *
	 * Clean implementation with proper data context for supplier-specific filtering.
	 *
	 * @param array $data Filter data with full supplier context.
	 * @return array Modified data with batched style IDs.
	 */
	public static function batch_style_ids( $data ) {
		// Only process SNS Activewear and style batching events.
		if ( ! self::is_sns_activewear_supplier( $data['supplier_id'] ) ) {
			return $data;
		}

		// Check if this is a products API call.
		$products_url_pattern = '/ssactivewear\.com\/v2\/products/';
		if ( ! preg_match( $products_url_pattern, $data['request_url'] ) ) {
			return $data;
		}

		$supplier_id = $data['supplier_id'];
		$cache_file  = self::get_cache_file_path();

		// Create cache if doesn't exist.
		if ( ! file_exists( $cache_file ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: No styles cache found - fetching all styles from API' );
			$authorization_config = Sync_Details_DAO::get_instance()->get_authorization_details( $data['supplier_id'] );
			$styles_data          = self::fetch_and_cache_styles( $supplier_id, $authorization_config, $cache_file, $data );
			if ( is_wp_error( $styles_data ) ) {
				Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to fetch styles from API' );
				Supplier_Logger::debug( $supplier_id, 'Error: ' . $styles_data->get_error_message() );
				return $data;
			}
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Successfully cached ' . $styles_data['styles_cached'] . ' styles' );
		}

		// Process cached styles.
		$cached_styles = self::read_cached_styles( $cache_file );
		if ( empty( $cached_styles ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: No cached styles available' );
			return $data;
		}

		$batch_styles = array_slice( $cached_styles, 0, 250 );
		$style_ids    = implode( ',', $batch_styles );

		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Processing batch of ' . count( $batch_styles ) . ' styles' );
		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Remaining styles in cache: ' . ( count( $cached_styles ) - 250 ) );

		// Modify URL with batched style IDs.
		$base_url = str_replace( '{{styleIDs}}', $style_ids, $data['request_url'] );

		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: API URL: ' . $base_url );
		$data['request_url'] = $base_url;
		return $data;
	}

	/**
	 * Fetch all styles from API and cache them to file.
	 *
	 * @param int    $supplier_id Supplier ID.
	 * @param array  $authorization_config Authorization configuration.
	 * @param string $cache_file Path to cache file.
	 * @param array  $data Filter data array.
	 * @return array Success array or error.
	 */
	private static function fetch_and_cache_styles( $supplier_id, $authorization_config, $cache_file, $data ) {
		$preview_mode       = Preview_Filters_Model::get_by_supplier_id( $supplier_id );
		$preview_cache_file = MOWCPS_PLUGIN_DIR . 'data' . DIRECTORY_SEPARATOR . 'preview-cache' . DIRECTORY_SEPARATOR . 'filterFiles' . DIRECTORY_SEPARATOR . 'supplier_' . $supplier_id . '_filter.json';

		// Preview mode active (non-revert): use cached preview style IDs directly.
		if ( $preview_mode['is_active'] && ! $preview_mode['is_revert_mode'] ) {
			$style_ids = json_decode( file_get_contents( $preview_cache_file ), true );

			if ( ! self::write_cache_file( $cache_file, $style_ids ) ) {
				Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to write styles cache' );
				return $data;
			}

			return array( 'styles_cached' => count( $style_ids ) );
		}

		$connection_model = new Connection_Model();
		$connections      = $connection_model->get_by_supplier_id( $supplier_id );

		// Find styles connection (priority 0 or 1, styles endpoint).
		$styles_connection = null;

		foreach ( $connections as $connection ) {
			if ( false !== strpos( $connection['configuration']['endpoint'], 'styles' ) ) {
				$styles_connection = $connection;
				break;
			}
		}

		if ( empty( $styles_connection ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: No active styles connection found' );
			return $data;
		}

		if ( $styles_connection['configuration']['auth_enabled'] ) {
			// Generate auth headers.
			$auth_service = new Authorization_Service();
			$headers      = $auth_service->generate_headers( $authorization_config, $supplier_id );
		} else {
			$headers = array();
		}

		if ( is_wp_error( $headers ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to generate auth headers - ' . $headers->get_error_message() );
			return $data;
		}

		// Prepare styles API request.
		$styles_endpoint = str_replace( '?styleid={{styleID}}', '', $styles_connection['configuration']['endpoint'] );
		$api_request     = array(
			'endpoint' => $styles_endpoint,
			'method'   => $styles_connection['configuration']['method'],
			'headers'  => array_merge( $headers, $styles_connection['configuration']['headers'] ),
			'body'     => $styles_connection['configuration']['body'],
			'timeout'  => 300,
		);

		// Make styles API call.
		$response = API_Service::make_api_call( $api_request['endpoint'], $api_request );

		if ( is_wp_error( $response ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to fetch styles - ' . $response->get_error_message() );
			return $data;
		}

		$response = json_decode( wp_remote_retrieve_body( $response ), true );

		// Get selected categories from supplier details.
		$supplier_details    = Sync_Details_DAO::get_instance()->get_supplier_details( $supplier_id );
		$supplier_details    = json_decode( $supplier_details['details'], true );
		$selected_categories = ! empty( $supplier_details['categories'] ) ? $supplier_details['categories'] : array();

		$selected_brands = ! empty( $supplier_details['brands'] ) ? $supplier_details['brands'] : array();

		// Prepare filters for category filtering.
		$filters = array(
			'conditions'       => array(
				array(
					'wc_attribute'       => 'baseCategory',
					'operator'           => Constants::EQUAL,
					'supplier_attribute' => $selected_categories,
				),
				array(
					'wc_attribute'       => 'brandName',
					'operator'           => Constants::EQUAL,
					'supplier_attribute' => $selected_brands,
				),
			),
			'combine_operator' => array(
				Constants::AND_CONDITION,
			),
		);

		// Filter data using the service.
		$filtered_data = Filter_Data_Services::filter_data( $response, $filters );

		// Extract style IDs from filtered data.
		$style_ids = array();
		if ( is_array( $filtered_data ) ) {
			foreach ( $filtered_data as $style ) {
				if ( isset( $style['styleID'] ) ) {
					$style_ids[] = $style['styleID'];
				}
			}
		}

		if ( empty( $style_ids ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: No style IDs found in response' );
			return $data;
		}

		// Revert mode: exclude previously previewed style IDs.
		if ( $preview_mode['is_active'] && $preview_mode['is_revert_mode'] ) {
			$preview_style_ids = json_decode( file_get_contents( $preview_cache_file ), true );
			$style_ids         = array_values( array_diff( $style_ids, $preview_style_ids ) );
		}

		// Cache style IDs to file with file locking.
		if ( ! self::write_cache_file( $cache_file, $style_ids ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to write styles cache' );
			return $data;
		}

		return array( 'styles_cached' => count( $style_ids ) );
	}

	/**
	 * Read cached styles from file.
	 *
	 * @param string $cache_file Path to cache file.
	 * @return array Array of style IDs.
	 */
	private static function read_cached_styles( $cache_file ) {
		if ( ! file_exists( $cache_file ) ) {
			return array();
		}

		$contents = file_get_contents( $cache_file );
		if ( false === $contents ) {
			return array();
		}

		$styles = json_decode( $contents, true );
		return is_array( $styles ) ? $styles : array();
	}

	/**
	 * Remove processed styles from cache file.
	 *
	 * @param int $count Number of styles to remove from beginning.
	 * @return bool Success status.
	 */
	public static function remove_processed_styles( $count = 250 ) {
		$supplier_id = Sync_Details_DAO::get_instance()->get_supplier_id();
		$cache_file  = self::get_cache_file_path();

		if ( ! file_exists( $cache_file ) ) {
			return true; // Nothing to remove.
		}

		$cached_styles = self::read_cached_styles( $cache_file );
		if ( empty( $cached_styles ) ) {
			// Clean up empty file.
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Cleaning up empty styles cache file' );
			wp_delete_file( $cache_file );
			return true;
		}

		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Removing ' . $count . ' processed styles from cache' );

		// Remove processed styles.
		$remaining_styles = array_slice( $cached_styles, $count );

		if ( empty( $remaining_styles ) ) {
			// All styles processed, remove cache file.
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: All styles processed - removing cache file' );
			wp_delete_file( $cache_file );
			return true;
		}

		// Update cache with remaining styles.
		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Updated cache with ' . count( $remaining_styles ) . ' remaining styles' );
		return self::write_cache_file( $cache_file, $remaining_styles );
	}

	/**
	 * Handle altering initial and previous batch values for SNS Activewear.
	 *
	 * This filter implementation handles the 'alter_initial_and_previous_batch_value' event
	 * to customize batch processing behavior for SNS Activewear supplier.
	 *
	 * @param array $data Filter data with batch context.
	 * @return array Modified data with updated initial and prev_batch values.
	 */
	public static function handle_alter_initial_and_previous_batch_value( $data ) {
		$supplier_id = $data['supplier_id'];
		$initial     = $data['initial'];
		$batch_flag  = $data['batch_flag'];
		$prev_batch  = $data['prev_batch'];

		// Get batch information from the data context.
		$batch_number              = isset( $data['batch_number'] ) ? $data['batch_number'] : 1;
		$batch_size                = isset( $data['batch_size'] ) ? $data['batch_size'] : 50;
		$last_batch_product_parsed = isset( $data['last_batch_product_parsed'] ) ? $data['last_batch_product_parsed'] : 0;

		// Get the stored reset batch number and new API data flag.
		$reset_batch_number = get_transient( 'mowcps_sns_reset_batch_' . $supplier_id );
		$has_new_api_data   = get_transient( 'mowcps_sns_new_api_data_' . $supplier_id );

		$sync_analytics = Sync_Details_DAO::get_instance()->get_supplier_analytics( $supplier_id, null );

		$old_product_count = get_option( 'mowcps_' . $data['connection_id'] . '_api_response_count_temp' );
		$new_product_count = get_option( 'mowcps_' . $data['connection_id'] . '_api_response_count' );

		$total_products_available = (int) $sync_analytics->total_product_parsed - (int) $old_product_count;

		// For SNS Activewear, we want to ensure proper batch processing.
		if ( $batch_flag ) {
			// Check if we have new API data flag set (from cache refresh) and we're not in the first batch.
			if ( 1 !== $batch_number && $has_new_api_data && false === $reset_batch_number ) {
				$data['initial']    = 0;
				$data['prev_batch'] = false; // Use false since we're starting fresh with new API data.

				update_option( 'mowcps_' . $data['connection_id'] . '_api_response_count_temp', $old_product_count + $new_product_count );

				// Store the batch number where we reset the initial value.
				set_transient( 'mowcps_sns_reset_batch_' . $supplier_id, $batch_number );

				// Clear the new API data flag since we've handled it.
				delete_transient( 'mowcps_sns_new_api_data_' . $supplier_id );
			} elseif ( false !== $reset_batch_number && $batch_number > $reset_batch_number ) {
				$batches_since_reset = $batch_number - $reset_batch_number;
				$data['initial']     = $batches_since_reset * $batch_size;
				$data['prev_batch']  = false; // Use false since we're working with fresh API data, not continuing a partial batch.
			} elseif ( 1 === $batch_number ) {
				update_option( 'mowcps_' . $data['connection_id'] . '_api_response_count_temp', $new_product_count );
			}
		}

		return $data;
	}

	/**
	 * Handle SNS Activewear batch completion logic.
	 *
	 * Determines if the current 250 style IDs batch was fully processed and manages
	 * cache accordingly. The sync process handles duplicate detection automatically.
	 *
	 * @param array $data Filter data with batch completion context.
	 * @return array Modified data with updated next process flag.
	 */
	public static function handle_batch_completion( $data ) {
		$next_process_flag = $data['next_process_flag'];
		$supplier_id       = Sync_Details_DAO::get_instance()->get_supplier_id();
		$batch_number      = isset( $data['batch_number'] ) ? (int) $data['batch_number'] : 1;

		if ( ! self::is_sns_activewear_supplier( $supplier_id ) || Plugin_Filters::EVENT_SUPPLIER_BATCH_COMPLETION !== $data['event_specifier'] ) {
			return $data;
		}

		// Check if batch is fully completed (all products in current 250-style batch processed).
		$batch_fully_completed = self::is_batch_fully_completed( $batch_number );
		set_transient( 'mowcps_sns_batch_fully_completed_' . $supplier_id, $batch_fully_completed );
		if ( ! $batch_fully_completed ) {
			return $data;
		}

		// Check if there's already a batch processing lock to prevent duplicate batches.
		$batch_lock_key = 'mowcps_sns_batch_processing_' . $supplier_id;
		if ( get_transient( $batch_lock_key ) ) {
			return $data;
		}

		// Set a short-term lock to prevent duplicate batch processing.
		set_transient( $batch_lock_key, true ); // 30 seconds lock

		// For SNS Activewear, we want to continue batching as long as there are style IDs.
		$remaining_styles = self::get_remaining_styles_count();

		if ( $remaining_styles > 250 ) {

			// Calculate how many styles will be in the next batch (styles 251-500 from current cache).
			$next_batch_size = min( 250, $remaining_styles - 250 );

			// Refresh API response data for the next batch BEFORE removing current batch.
			$refresh_success = self::refresh_api_response_for_next_batch( $supplier_id );

			if ( $refresh_success ) {
				// API refresh successful, now remove the processed batch of 250 styles from cache.
				$removal_success = self::remove_processed_styles( 250 );

				// Override the flag to continue with next batch - framework will handle cron scheduling.
				$data['next_process_flag'] = Constants::INITIATE_BATCH_SYNC;
			}
		}

		// Clear the batch processing lock.
		delete_transient( $batch_lock_key );
		return $data;
	}

	/**
	 * Check if the current batch of 250 style IDs is fully completed.
	 *
	 * Uses batch number comparison instead of style ID tracking to handle
	 * discontinued styles that return 0 products from the API.
	 *
	 * @param int $batch_number   Current batch number.
	 * @return bool True if batch is fully completed.
	 */
	private static function is_batch_fully_completed( $batch_number ) {
		$supplier_id   = Sync_Details_DAO::get_instance()->get_supplier_id();
		$connection_id = Sync_Details_DAO::get_instance()->get_connection_id();

		// Get batch size from settings.
		$batch_size = Sync_Details_DAO::get_instance()->get_batch_size();
		if ( empty( $batch_size ) || $batch_size <= 0 ) {
			return false;
		}

		// Get API response count for current 250-style batch.
		$api_response_count = (int) get_option( 'mowcps_' . $connection_id . '_api_response_count', 0 );
		if ( $api_response_count <= 0 ) {
			// No products to process, batch is complete.
			return true;
		}

		// Calculate total batches needed for current API response.
		$total_batches = (int) ceil( $api_response_count / $batch_size );

		// Get the batch number where the last 250-style reset occurred.
		$reset_batch_number = get_transient( 'mowcps_sns_reset_batch_' . $supplier_id );

		// Calculate effective batch number relative to current 250-style set.
		// If reset_batch_number exists, we need to calculate the relative batch number.
		// Otherwise, we're on the first 250-style set, so batch_number is the effective batch.
		if ( false !== $reset_batch_number ) {
			$effective_batch = ( $batch_number - $reset_batch_number ) + 1;
		} else {
			$effective_batch = $batch_number;
		}

		return $effective_batch >= $total_batches;
	}

	/**
	 * Refresh API response for the next batch of style IDs.
	 *
	 * @param int $supplier_id Supplier ID.
	 * @return bool True on success, false on failure.
	 */
	private static function refresh_api_response_for_next_batch( $supplier_id ) {
		// Set a flag that the next batch should reset initial to 0 because we have new API data.
		set_transient( 'mowcps_sns_new_api_data_' . $supplier_id, true, 300 ); // 5 minute expiry.

		// Clean up the reset batch transient when starting a new set of style IDs.
		delete_transient( 'mowcps_sns_reset_batch_' . $supplier_id );

		// Get the next 250 style IDs from cache.
		$cache_file = self::get_cache_file_path();
		if ( ! file_exists( $cache_file ) ) {
			return false;
		}

		$cached_styles = json_decode( file_get_contents( $cache_file ), true );
		if ( ! is_array( $cached_styles ) || empty( $cached_styles ) ) {
			return false;
		}

		// Get the NEXT batch of styles (styles 251-500), not the current batch (styles 1-250).
		$next_batch_styles = array_slice( $cached_styles, 250, 250 );

		// Safety check: ensure there are actually styles in the next batch.
		if ( empty( $next_batch_styles ) ) {
			return false;
		}

		// Get connection details.
		$connection_id       = Sync_Details_DAO::get_instance()->get_connection_id();
		$products_connection = Connection_Model::get_by_id( $connection_id );

		if ( empty( $products_connection ) ) {
			return false;
		}

		// Build API URL with next batch style IDs.
		$style_ids_param = implode( ',', $next_batch_styles );
		$base_url        = str_replace( '{{styleIDs}}', $style_ids_param, $products_connection['configuration']['endpoint'] );

		$authorization_config = Sync_Details_DAO::get_instance()->get_authorization_details( $supplier_id );

		if ( $products_connection['configuration']['auth_enabled'] ) {
			$auth_service = new Authorization_Service();
			$headers      = $auth_service->generate_headers( $authorization_config, $supplier_id );
		} else {
			$headers = array();
		}

		// Make API call.
		$api_request  = array(
			'endpoint' => $base_url,
			'method'   => $products_connection['configuration']['method'],
			'headers'  => array_merge( $headers, $products_connection['configuration']['headers'] ),
			'body'     => $products_connection['configuration']['body'],
			'timeout'  => 60,
		);
		$api_response = API_Service::make_api_call( $api_request['endpoint'], $api_request );

		if ( is_wp_error( $api_response ) ) {
			return false;
		}

		$response_code = wp_remote_retrieve_response_code( $api_response );

		// Extract response body.
		$response_body = wp_remote_retrieve_body( $api_response );
		if ( empty( $response_body ) ) {
			return false;
		}

		// Check if response is serialized data (from SNS API).
		if ( is_serialized( $response_body ) ) {
			$api_data = maybe_unserialize( $response_body );
		} else {
			// Try JSON decode as fallback.
			$api_data = json_decode( $response_body, true );
		}
		if ( ! is_array( $api_data ) ) {
			return false;
		}

		$original_product_count = count( $api_data );
		update_option( 'mowcps_' . $products_connection['id'] . '_api_response_count', $original_product_count );
		// Apply product data filters before storing.
		$preview_filter = Preview_Filters_Model::get_by_supplier_id( $supplier_id );
		if ( $preview_filter && $preview_filter['is_active'] ) {
			$api_data = Preview_Filter_Service::filter_products( $api_data, $supplier_id, $products_connection['id'], $preview_filter );
		} elseif ( has_filter( Plugin_Filters::FILTER_PRODUCT_DATA_FILTER ) ) {
			$api_data = apply_filters(
				Plugin_Filters::FILTER_PRODUCT_DATA_FILTER,
				$api_data,
				$supplier_id,
				$products_connection['id']
			);
		}
		if ( has_filter( Plugin_Filters::FILTER_LIMIT__PRODUCTS_FOR_SANDBOX ) ) {
			$api_data = apply_filters(
				Plugin_Filters::FILTER_LIMIT__PRODUCTS_FOR_SANDBOX,
				array(
					'event_specifier' => Plugin_Filters::EVENT_LIMIT_PRIMARY_API_PRODUCTS,
					'api_response'    => $api_data,
					'connection_id'   => $products_connection['id'],
					'supplier_id'     => $supplier_id,
					'page_number'     => 1,
					'additional_info' => array(),
				)
			);
		}
		$batch_flag_setting = Setting_Model::get_by_connection_and_operation( $products_connection['id'], 'batch', array( 'configuration' => true ) );
		$is_batch_enabled   = isset( $batch_flag_setting['is_active'] ) && '1' === $batch_flag_setting['is_active'];
		$batch_size         = isset( $batch_flag_setting['configuration']['batch_size'] ) ? (int) $batch_flag_setting['configuration']['batch_size'] : 100;

		// Store the filtered API response.
		if ( $is_batch_enabled && $batch_size > 0 ) {
			$current_total_batches = (int) get_option( 'mowcps_' . $products_connection['id'] . '_total_batches', 0 );
			$starting_batch_number = $current_total_batches + 1;
			$file_directory_path   = MOWCPS_PLUGIN_DIR . 'data' . DIRECTORY_SEPARATOR . 'external-supplier' . DIRECTORY_SEPARATOR . 'api-response';
			$write_success         = Sync_Utils::store_data_in_batch_files( $api_data, $file_directory_path, $products_connection['id'], $batch_size, $supplier_id, $starting_batch_number );
		} else {
			$sync_file     = MOWCPS_PLUGIN_DIR . 'data/external-supplier/api-response/sync_' . $products_connection['id'] . '.json';
			$write_success = file_put_contents( $sync_file, serialize( $api_data ) );
		}

		if ( false === $write_success ) {
			return false;
		}

		// Update the API response count for the new batch data.
		$new_product_count = is_array( $api_data ) ? count( $api_data ) : 0;
		update_option( 'mowcps_' . $products_connection['id'] . '_filter_api_response_count', $new_product_count );
		$new_current_product_count  = get_option( 'mowcps_' . $products_connection['id'] . '_current_api_response_count', 0 );
		$new_current_product_count += $new_product_count;
		update_option( 'mowcps_' . $products_connection['id'] . '_current_api_response_count', $new_current_product_count );
		return true;
	}

	/**
	 * Check if current supplier is SNS Activewear.
	 *
	 * @param int $supplier_id Supplier ID to check.
	 * @return bool True if SNS Activewear, false otherwise.
	 */
	private static function is_sns_activewear_supplier( $supplier_id ) {
		$supplier_data = Sync_Details_DAO::get_instance()->get_supplier_details( $supplier_id );
		return ( ! empty( $supplier_data['app_type'] ) && Constants::SUPPLIER_APP_TYPE_SNS_ACTIVEWEAR === $supplier_data['app_type'] );
	}

	/**
	 * Get the cache file path for SNS Activewear styles.
	 *
	 * @return string Cache file path.
	 */
	private static function get_cache_file_path() {
		$cache_dir = MOWCPS_PLUGIN_DIR . 'cache' . DIRECTORY_SEPARATOR;

		// Create cache directory if it doesn't exist.
		if ( ! file_exists( $cache_dir ) ) {
			wp_mkdir_p( $cache_dir );
		}

		return $cache_dir . 'sns_activewear_styles_cache.json';
	}

	/**
	 * Get count of remaining styles in cache.
	 *
	 * @return int Number of remaining styles.
	 */
	public static function get_remaining_styles_count() {
		$cache_file    = self::get_cache_file_path();
		$cached_styles = self::read_cached_styles( $cache_file );
		return count( $cached_styles );
	}

	/**
	 * Write data to a file atomically using a temporary file and rename.
	 *
	 * This function ensures thread-safe, atomic file writes by:
	 * 1. Writing to a temporary file with exclusive locking
	 * 2. Using rename() for atomic replacement of the target file
	 * 3. Cleaning up the temporary file if anything fails
	 *
	 * @param string $target_file Path to the target file.
	 * @param mixed  $data Data to write (will be JSON encoded).
	 * @return bool True on success, false on failure.
	 */
	private static function write_cache_file( $target_file, $data ) {
		$temp_file = $target_file . '.tmp';
		$result    = file_put_contents( $temp_file, wp_json_encode( $data, JSON_PRETTY_PRINT ), LOCK_EX );

		if ( false === $result ) {
			return false;
		}

		if ( ! rename( $temp_file, $target_file ) ) {
			wp_delete_file( $temp_file );
			return false;
		}

		return true;
	}

	/**
	 * Extract unique base categories from API response.
	 *
	 * @param array $response API response data.
	 * @return array Array of unique base categories.
	 */
	private static function extract_categories_and_brands( $response ) {
		$categories         = array();
		$category_brand_map = array();

		if ( ! empty( $response ) && is_array( $response ) ) {
			foreach ( $response as $item ) {
				$category = $item['baseCategory'];
				$brand    = $item['brandName'];

				// Collect unique categories
				if ( ! isset( $category_brand_map[ $category ] ) ) {
					$categories[]                    = $category;
					$category_brand_map[ $category ] = array();
				}

				// Add unique brand to category
				if ( ! in_array( $brand, $category_brand_map[ $category ], true ) ) {
					$category_brand_map[ $category ][] = $brand;
				}
			}
		}

		return array(
			'categories'         => $categories,
			'category_brand_map' => $category_brand_map,
		);
	}
	/**
	 * Handle altering sync end status for SNS Activewear.
	 *
	 * @param array $data Filter data with sync end status context.
	 * @return array Modified data with updated sync end status.
	 */
	public static function handle_alter_sync_end_status( $data ) {
		$remaining_styles = self::get_remaining_styles_count();
		$supplier_id      = $data['supplier_id'];

		// Check if this is an SNS Activewear supplier.
		if ( ! self::is_sns_activewear_supplier( $supplier_id ) ) {
			return $data;
		}

		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Checking sync completion status' );
		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Remaining styles to process: ' . $remaining_styles );

		// SNS Activewear batch size is 250.
		if ( $remaining_styles > 250 ) {
			$data['sync_end_status'] = 'Batch ' . $data['batch_number'] . ' completed';
		} else {
			delete_transient( 'mowcps_sns_reset_batch_' . $data['supplier_id'] );
			delete_transient( 'mowcps_sns_batch_fully_completed_' . $data['supplier_id'] );

			delete_option( 'mowcps_' . $data['connection_id'] . '_api_response_count_temp' );
			// Clear the style transient cache
			$last_cached_style_option = 'sns_last_cached_style_' . $supplier_id;
			$last_cached_style_id     = get_option( $last_cached_style_option );

			if ( $last_cached_style_id ) {
				$cache_key = 'sns_style_data_' . $supplier_id . '_' . $last_cached_style_id;
				delete_transient( $cache_key );
				delete_option( $last_cached_style_option );
			}
			// Clean up cache file when sync is complete.
			$cache_file = self::get_cache_file_path();
			if ( file_exists( $cache_file ) ) {
				wp_delete_file( $cache_file );
			}

			$data['sync_end_status'] = 'Sync Completed';
		}
		if ( has_filter( Plugin_Filters::FILTER_LIMIT__PRODUCTS_FOR_SANDBOX ) ) {
			$data['sync_end_status'] = apply_filters(
				Plugin_Filters::FILTER_LIMIT__PRODUCTS_FOR_SANDBOX,
				array(
					'event_specifier' => Plugin_Filters::EVENT_LIMIT_PRIMARY_API_PRODUCTS,
					'sync_data'       => $data,
					'supplier_id'     => $supplier_id,
					'connection_id'   => $data['connection_id'],
					'additional_info' => array(
						'sync_end_status' => $data['sync_end_status'],
					),
				)
			);
		}
		return $data;
	}

	/**
	 * Handle altering batch number for SNS Activewear.
	 *
	 * @param array $data Filter data with batch number context.
	 * @return array Modified data with updated batch number.
	 */
	public static function handle_alter_batch_number( $data ) {
		$reset_batch_number = get_transient( 'mowcps_sns_reset_batch_' . $data['supplier_id'] );

		if ( $reset_batch_number ) {
			$data['new_batch_number'] = ( $data['new_batch_number'] - $reset_batch_number ) + 1;
		}
		return $data;
	}

	/**
	 * Handle altering product sync audit for SNS Activewear.
	 *
	 * @param array $data Filter data with product sync audit context.
	 * @return array Modified data with updated product sync audit.
	 */
	public static function handle_alter_product_sync_audit( $data ) {
		if ( ! isset( $data['event_specifier'] ) ) {
			return $data;
		}

		if ( Plugin_Filters::EVENT_ALTER_SYNC_END_STATUS === $data['event_specifier'] ) {
			$data = self::handle_alter_sync_end_status( $data );
		} elseif ( Plugin_Filters::EVENT_ALTER_INITIAL_AND_PREVIOUS_BATCH_VALUE === $data['event_specifier'] ) {
			$data = self::handle_alter_initial_and_previous_batch_value( $data );
		} elseif ( Plugin_Filters::EVENT_ALTER_BATCH_NUMBER === $data['event_specifier'] ) {
			$data = self::handle_alter_batch_number( $data );
		}

		return $data;
	}

	/**
	 * Handle stopping sync for SNS Activewear.
	 *
	 * @param array $cron_args Cron arguments.
	 * @return void
	 */
	public static function handle_stop_sync( $cron_args ) {
		// Ensure we have the required supplier_id.
		if ( ! isset( $cron_args['supplier_id'] ) || empty( $cron_args['supplier_id'] ) ) {
			return;
		}

		$supplier_id = $cron_args['supplier_id'];

		// Check if this is an SNS Activewear supplier.
		if ( ! self::is_sns_activewear_supplier( $supplier_id ) ) {
			return;
		}

		// Delete options with LIKE condition since connection ID may not be available.
		global $wpdb;
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				'mowcps_%_api_response_count_temp'
			)
		);

		// Delete SNS-specific transients.
		delete_transient( 'mowcps_sns_batch_fully_completed_' . $supplier_id );

		// Clean up cache file when sync is stopped.
		$cache_file = self::get_cache_file_path();
		if ( file_exists( $cache_file ) ) {
			wp_delete_file( $cache_file );
		}
	}

	/**
	 * Handle preview attribute mapping for SNS Activewear
	 *
	 * Provides hardcoded attribute mappings for /v2/styles endpoint data
	 * This ensures the preview displays correct fields from the styles API
	 *
	 * @param array $data Attribute mapping data.
	 * @return array Modified data with SNS attribute mappings.
	 */
	public static function handle_preview_attribute_mapping( $data ) {
		// Only handle for SNS Activewear.
		if ( ! self::is_sns_activewear_supplier( $data['supplier_id'] ) ) {
			return $data;
		}

		Supplier_Logger::debug( $data['supplier_id'], 'SNS Activewear: Applying hardcoded attribute mappings for preview' );

		// Hardcoded attribute mappings for /v2/styles endpoint
		// These map the styles API fields to WooCommerce-like attributes
		$sns_preview_mappings = array(
			array(
				'wc_attribute'       => 'sku',
				'supplier_attribute' => 'styleID',
				'product_type'       => 'parent',
			),
			array(
				'wc_attribute'       => 'name',
				'supplier_attribute' => 'title',
				'product_type'       => 'parent',
			),
			array(
				'wc_attribute'       => 'description',
				'supplier_attribute' => 'description',
				'product_type'       => 'parent',
			),
			array(
				'wc_attribute'       => 'categories',
				'supplier_attribute' => 'baseCategory',
				'product_type'       => 'parent',
			),
			array(
				'wc_attribute'       => 'images',
				'supplier_attribute' => 'styleImage',
				'product_type'       => 'parent',
			),
		);

		// Mark that we're overriding the mappings
		$data['override_mappings']  = true;
		$data['attribute_mappings'] = $sns_preview_mappings;

		return $data;
	}

	/**
	 * Handle preview products fetch for SNS Activewear
	 *
	 * SNS uses /v2/styles endpoint for preview instead of /v2/products
	 * This shows parent products (styles) with basic info
	 *
	 * @param array $data Preview fetch data.
	 * @return array Modified data with SNS products.
	 */
	public static function handle_preview_products_fetch( $data ) {
		// Only handle for SNS Activewear.
		if ( ! self::is_sns_activewear_supplier( $data['supplier_id'] ) ) {
			return $data;
		}

		$supplier_id = $data['supplier_id'];

		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Using /v2/styles endpoint for preview' );

		// Get authorization configuration.
		$connection_model = new Connection_Model();
		$connections      = $connection_model->get_by_supplier_id( $supplier_id );

		// Find styles connection.
		$styles_connection = null;
		foreach ( $connections as $connection ) {
			if ( false !== strpos( $connection['configuration']['endpoint'], '/v2/styles' ) ) {
				$styles_connection = $connection;
				break;
			}
		}

		if ( empty( $styles_connection ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: No styles connection found for preview' );
			return $data;
		}

		// Get supplier details for authorization.
		$supplier_details    = Sync_Details_DAO::get_instance()->get_supplier_details( $supplier_id );
		$supplier_config     = json_decode( $supplier_details['details'], true );
		$selected_categories = ! empty( $supplier_config['categories'] ) ? $supplier_config['categories'] : array();
		$selected_brands     = ! empty( $supplier_config['brands'] ) ? $supplier_config['brands'] : array();

		$auth_settings = array(
			'client_id'     => $supplier_config['account_number'] ?? '',
			'client_secret' => $supplier_config['api_key'] ?? '',
		);
		// Prepare authorization headers.
		$authorization_config = array(
			'auth_type'     => 'basic',
			'auth_settings' => $auth_settings,
		);

		$auth_service = new Authorization_Service();
		$headers      = $auth_service->generate_headers( $authorization_config, $supplier_id );

		if ( is_wp_error( $headers ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to generate auth headers - ' . $headers->get_error_message() );
			return $data;
		}

		// Prepare styles API request.
		$styles_endpoint = str_replace( '?styleid={{styleID}}', '', $styles_connection['configuration']['endpoint'] );
		$api_request     = array(
			'endpoint' => $styles_endpoint,
			'method'   => $styles_connection['configuration']['method'],
			'headers'  => array_merge( $headers, $styles_connection['configuration']['headers'] ),
			'body'     => $styles_connection['configuration']['body'],
			'timeout'  => 300,
		);

		// Make styles API call.
		$response = API_Service::make_api_call( $api_request['endpoint'], $api_request );

		if ( is_wp_error( $response ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Failed to fetch styles - ' . $response->get_error_message() );
			return $data;
		}

		$response_body = wp_remote_retrieve_body( $response );
		$api_data      = json_decode( $response_body, true );

		if ( ! is_array( $api_data ) || empty( $api_data ) ) {
			Supplier_Logger::debug( $supplier_id, 'SNS Activewear: No styles data in response' );
			return $data;
		}

		// Filter by selected categories if any.
		if ( ! empty( $selected_categories ) ) {
			$filters  = array(
				'conditions'       => array(
					array(
						'wc_attribute'       => 'baseCategory',
						'operator'           => Constants::EQUAL,
						'supplier_attribute' => $selected_categories,
					),
					array(
						'wc_attribute'       => 'brandName',
						'operator'           => Constants::EQUAL,
						'supplier_attribute' => $selected_brands,
					),
				),
				'combine_operator' => array(
					Constants::AND_CONDITION,
				),
			);
			$api_data = Filter_Data_Services::filter_data( $api_data, $filters );
		}

		// Transform SNS styles data to match expected format for preview.
		$transformed_products = array();
		foreach ( $api_data as $style ) {
			$transformed_products[] = array(
				'styleID'      => $style['styleID'] ?? '',
				'title'        => $style['title'] ?? '',
				'description'  => $style['description'] ?? '',
				'baseCategory' => $style['baseCategory'] ?? '',
				'styleImage'   => ! empty( $style['styleImage'] ) ? 'https://cdn.ssactivewear.com/' . $style['styleImage'] : '',
			);
		}

		Supplier_Logger::debug( $supplier_id, 'SNS Activewear: Fetched ' . count( $transformed_products ) . ' styles for preview' );

		// Mark that we've handled the fetch.
		$data['skip_default_fetch'] = true;
		$data['products']           = $transformed_products;
		$data['pagination_info']    = array(
			'has_pagination' => false, // SNS /v2/styles returns all styles in one call
			'total_products' => count( $transformed_products ),
		);

		return $data;
	}

	/**
	 * Add thumbnail image for SNS Activewear products.
	 *
	 * @param array $data Product data.
	 * @return array Modified product data with thumbnail image.
	 */
	public static function add_thumbnail_image( $data ) {
		if ( empty( $data['event_specifier'] ) || Plugin_Filters::EVENT_ADD_SNS_THUMBNAIL_IMAGE !== $data['event_specifier'] ) {
			return $data;
		}
		$product_data   = $data['product_data'];
		$style_code     = $product_data['styleID'];
		$full_image_url = 'https://cdn.ssactivewear.com/Images/Style/' . $style_code . '_fm.jpg';
		$new_image      = array( $full_image_url );
		$data['images'] = $new_image;
		return $data;
	}

	/**
	 * Update brands for SNS Activewear.
	 *
	 * @param array $supplier Supplier data.
	 * @return void
	 */
	public function update_brands( $supplier ) {
		if ( empty( $supplier['details']['brands'] ) ) {
			$supplier[ Constants::AUTHORIZATION ] = Authorization_Model::get_by_supplier_id( $supplier['id'] );
			$supplier[ Constants::CONNECTIONS ]   = Connection_Model::get_by_supplier_id( $supplier['id'] );
			$details                             = is_string( $supplier['details'] ) ? json_decode( $supplier['details'], true ) : $supplier['details'];
			$country                             = $details['country'] ?? null;
			$response                             = $this->fetch_brands_and_categories( $supplier, $country );

			$response_body = json_decode( wp_remote_retrieve_body( $response ), true );

			$brands = array();
			foreach ( $response_body as $item ) {
				$category = $item['baseCategory'];
				$brand    = $item['brandName'];

				$categories = $supplier['details']['categories'];
				if ( in_array( $category, $categories ) && ! in_array( $brand, $brands, true ) ) {
					$brands[] = $brand;
				}
			}

			$supplier['details']['brands'] = $brands;
			$new_supplier['details']       = $supplier['details'];
			Supplier_Model::update( $supplier['uuid'], $new_supplier );
		}
	}

	/**
	 * Fetch brands and categories for SNS Activewear.
	 * When $country is provided, result is cached by country and cleared when that supplier is updated.
	 *
	 * @param array       $supplier Supplier data (with AUTHORIZATION and CONNECTIONS).
	 * @param string|null $country  Optional. Country code for caching (e.g. US, CA); omit to skip cache.
	 * @return array|WP_Error API response or error.
	 */
	private function fetch_brands_and_categories( $supplier, $country = null ) {
		$do_fetch = function () use ( $supplier ) {
			$authorization = $supplier[ Constants::AUTHORIZATION ] ?? array();
			if ( empty( $authorization ) ) {
				return new WP_Error(
					'sns_no_auth',
					sprintf( Constants::SUPPLIER_NO_AUTH_CONFIG, $this->supplier_name ),
				);
			}

			// Use Authorization Service to generate headers.
			$auth_service = new Authorization_Service();
			$headers      = $auth_service->generate_headers( $authorization, 0 );

			if ( is_wp_error( $headers ) ) {
				return new WP_Error(
					'sns_auth_failed',
					sprintf( Constants::SUPPLIER_AUTH_FAILED, $this->supplier_name, $headers->get_error_message() ),
				);
			}

			$connections     = $supplier['connections'] ?? array();
			$test_connection = $this->find_test_connection( $connections );

			if ( empty( $test_connection ) ) {
				return new WP_Error(
					'sns_no_endpoint',
					sprintf( Constants::SUPPLIER_NO_TEST_ENDPOINT, $this->supplier_name ),
				);
			}

			$config   = $test_connection['configuration'];
			$endpoint = str_replace( '?styleid={{styleID}}', '', $config['endpoint'] );
			$method   = $config['method'];
			$headers  = array_merge( $headers, $config['headers'] );
			$body     = $config['body'];

			if ( empty( $endpoint ) ) {
				return new WP_Error(
					'sns_no_endpoint',
					sprintf( Constants::SUPPLIER_NO_TEST_ENDPOINT_AVAILABLE, $this->supplier_name ),
				);
			}

			$api_request = array(
				'endpoint' => $endpoint,
				'method'   => $method,
				'headers'  => $headers,
				'body'     => $body,
				'timeout'  => 30,
			);

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

			if ( is_wp_error( $response ) ) {
				return $response;
			}

			if ( ! Utils::is_status_success( $response ) ) {
				return new WP_Error(
					'sns_api_error',
					wp_remote_retrieve_response_message( $response ),
					array( 'status' => wp_remote_retrieve_response_code( $response ) ),
				);
			}

			$body = wp_remote_retrieve_body( $response );
			$decoded = is_string( $body ) ? json_decode( $body, true ) : $body;
			return is_array( $decoded ) ? $decoded : array();
		};

		if ( null !== $country && '' !== $country ) {
			$key    = self::CACHE_KEY_PREFIX_CATEGORIES_BRANDS . $country;
			$cached = Cache_Service::remember( $key, Cache_Service::DEFAULT_EXPIRATION, $do_fetch );
			if ( is_wp_error( $cached ) ) {
				return $cached;
			}
			// Return a minimal WP response shape so callers can use wp_remote_retrieve_* unchanged.
			return array(
				'response' => array( 'code' => 200, 'message' => 'OK' ),
				'body'     => wp_json_encode( $cached ),
			);
		}

		$result = $do_fetch();
		if ( is_wp_error( $result ) ) {
			return $this->get_test_result( false, $result->get_error_message() );
		}
		// Same minimal response shape as cache hit for callers using wp_remote_retrieve_*.
		return array(
			'response' => array( 'code' => 200, 'message' => 'OK' ),
			'body'     => wp_json_encode( $result ),
		);
	}
}
