<?php

namespace AIPT\Api\BulkGenerator\SeoMeta;

use AIPT\Api\BulkGenerator\Common\AIClientFactory;
use AIPT\Api\BulkGenerator\Common\ProductDataFormatter;
use AIPT\Api\BulkGenerator\SeoMeta\Core\SeoMetaPromptFactory;
use AIPT\Api\BulkGenerator\SeoMeta\Core\SeoPluginDetector;
use AIPT\Core\ErrorHandler;
use AIPT\Utils\ProductFilter\ProductFilterBuilder;
use AIPT\Utils\ProductFilter\ProductFilterMapper;

class SeoMetaGeneratorService {

	private $prompt_factory;

	public function __construct() {
		$this->prompt_factory = new SeoMetaPromptFactory();
	}

	public function get_products( $params ) {
		try {
			$filter_params = ProductFilterMapper::fromRequest( $params );
			$builder       = new ProductFilterBuilder();
			ProductFilterMapper::applyToBuilder( $builder, $filter_params );
			return $builder->getProducts();
		} catch ( \Exception $e ) {

			$safe_keys = array( 'category', 'status', 'per_page', 'page', 'search', 'on_sale', 'stock_status' );
			$sanitized_params = array_intersect_key( $params ?? array(), array_flip( $safe_keys ) );

			return array(
				'products'    => array(),
				'total'       => 0,
				'total_pages' => 0,
				'error'       => 'An error occurred while searching products',
			);
		}
	}

	public function get_categories() {
		return ProductFilterMapper::getCategories();
	}

	public function bulk_generate( $product_ids, $options ) {
		$validation = $this->validate_product_ids( $product_ids );
		if ( ! $validation['valid'] ) {
			return array(
				array(
					'product_id' => 0,
					'error'      => $validation['error'],
				),
			);
		}

		$product_ids = $validation['ids'];
		$results     = array();
		$language    = isset( $options['language'] ) ? $options['language'] : \get_option( 'aipt_language', 'English' );

		$prompt_options  = $this->build_generation_options( $options );
		$detected_plugin = SeoPluginDetector::detect();
		$client          = $this->create_ai_client_for_product( $options, array(), false );

		foreach ( $product_ids as $product_id ) {
			$product = \wc_get_product( $product_id );
			if ( ! $product ) {
				$results[] = array(
					'product_id' => $product_id,
					'error'      => 'Product not found',
				);
				continue;
			}

			try {
				$result       = $this->process_product_seo( $product_id, $product, $detected_plugin, $prompt_options, $client, $language, $options );

				$results[] = $result;

			} catch ( \Exception $e ) {
				$provider = $options['provider'] ?? \get_option( 'aipt_api_provider', 'openai' );

				$results[] = array(
					'product_id'   => $product_id,
					'product_name' => $product->get_name(),
					'api_error'    => ErrorHandler::format_api_error_message( ucfirst( $provider ), $e->getMessage() ),
				);
			}
		}

		return $results;
	}

	/**
	 * Generate multiple variants of SEO meta for products.
	 *
	 * Generates 2-5 alternative SEO meta variants in a single API call.
	 * Default is 2 variants (free), up to 5 for premium users.
	 *
	 * @param array $product_ids Product IDs to generate variants for.
	 * @param array $options     Generation options including 'variantCount'.
	 *
	 * @return array Results with variants array for each product.
	 */
	public function bulk_generate_variants( $product_ids, $options ) {
		$validation = $this->validate_product_ids( $product_ids );
		if ( ! $validation['valid'] ) {
			return array(
				array(
					'product_id' => 0,
					'error'      => $validation['error'],
				),
			);
		}

		$product_ids = $validation['ids'];
		$results     = array();
		$language    = isset( $options['language'] ) ? $options['language'] : \get_option( 'aipt_language', 'English' );

		$max_variants  = \apply_filters( 'aipt_seo_meta_max_variants', 2 ); 
		$variant_count = isset( $options['variantCount'] ) ? (int) $options['variantCount'] : 2;
		$variant_count = max( 1, min( $max_variants, $variant_count ) );

		$prompt_options  = $this->build_generation_options( $options, $variant_count );
		$detected_plugin = SeoPluginDetector::detect();
		$client          = $this->create_ai_client_for_product( $options, array(), true );

		foreach ( $product_ids as $product_id ) {
			$product = \wc_get_product( $product_id );
			if ( ! $product ) {
				$results[] = array(
					'product_id' => $product_id,
					'error'      => 'Product not found',
				);
				continue;
			}

			try {
				$result       = $this->process_product_seo_variants( $product_id, $product, $detected_plugin, $prompt_options, $client, $language, $options );

				$results[] = $result;

			} catch ( \Exception $e ) {
				$provider_name = ucfirst( $options['provider'] ?? \get_option( 'aipt_api_provider', 'openai' ) );

				$results[] = array(
					'product_id'   => $product_id,
					'product_name' => $product->get_name(),
					'error'        => $provider_name . ' API Error: ' . $e->getMessage(),
				);
			}
		}

		return $results;
	}

	/**
	 * Validate and normalize product IDs.
	 *
	 * @param mixed $product_ids Product IDs to validate.
	 * @return array Array with 'valid' => bool and 'ids' => array or 'error' => string.
	 */
	private function validate_product_ids( $product_ids ) {
		if ( ! is_array( $product_ids ) ) {
			return array(
				'valid' => false,
				'error' => 'Invalid product_ids parameter: expected array',
			);
		}

		$normalized_ids = array_filter( array_map( 'absint', $product_ids ) );
		if ( empty( $normalized_ids ) ) {
			return array(
				'valid' => false,
				'error' => 'No valid product IDs provided',
			);
		}

		return array(
			'valid' => true,
			'ids'   => $normalized_ids,
		);
	}

	/**
	 * Build generation options with defaults and boolean flag enforcement.
	 *
	 * @param array $options User-provided options.
	 * @param int   $variant_count Optional variant count for variant generation.
	 * @return array Merged options with enforced boolean flags.
	 */
	private function build_generation_options( $options, $variant_count = null ) {
		$default_options = array(
			'tone'                 => 'Professional',
			'generateTitle'        => true,
			'generateDescription'  => true,
			'generateFocusKeyword' => false,
			'titleMaxLength'       => 60,
			'descMaxLength'        => 160,
			'additional_prompt'    => '',
		);

		if ( $variant_count !== null ) {
			$default_options['variantCount'] = $variant_count;
		}

		$filtered_options = \apply_filters( 'aipt_seo_meta_generator_options', $default_options, $options );

		$generate_title    = isset( $options['generateTitle'] ) ? (bool) $options['generateTitle'] : $filtered_options['generateTitle'];
		$generate_desc     = isset( $options['generateDescription'] ) ? (bool) $options['generateDescription'] : $filtered_options['generateDescription'];
		$generate_focus_kw = isset( $options['generateFocusKeyword'] ) ? (bool) $options['generateFocusKeyword'] : $filtered_options['generateFocusKeyword'];

		$merged_options = array_merge( $filtered_options, $options ?? array() );
		$prompt_options = array_merge(
			$merged_options,
			array(
				'generateTitle'        => $generate_title,
				'generateDescription'  => $generate_desc,
				'generateFocusKeyword' => $generate_focus_kw,
			)
		);

		return $prompt_options;
	}

	/**
	 * Create AI client for product generation.
	 *
	 * @param array $options User-provided options.
	 * @param array $product_data Formatted product data.
	 * @param bool  $is_variant Whether this is for variant generation.
	 * @return object AI client instance.
	 */
	private function create_ai_client_for_product( $options, $product_data, $is_variant = false ) {
		$api_provider = isset( $options['provider'] ) ? $options['provider'] : \get_option( 'aipt_api_provider', 'openai' );
		$generation_type = $is_variant ? 'seo_meta_variants' : 'seo_meta';

		return AIClientFactory::create(
			$api_provider,
			array(
				'category'        => $product_data['categories'] ?? '',
				'generation_type' => $generation_type,
				'model'           => $options['model'] ?? null,
			)
		);
	}

	/**
	 * Process SEO meta generation for a single product.
	 *
	 * @param int    $product_id Product ID.
	 * @param object $product WooCommerce product object.
	 * @param array  $detected_plugin Detected SEO plugin info.
	 * @param array  $prompt_options Prompt generation options.
	 * @param object $client AI client instance.
	 * @param string $language Target language.
	 * @param array  $options User-provided options.
	 * @return array Result array with generated content or error.
	 */
	private function process_product_seo( $product_id, $product, $detected_plugin, $prompt_options, $client, $language, $options ) {
		$product_data = ProductDataFormatter::getFormattedProductData( $product );

		$current_meta              = $this->get_current_seo_meta( $product_id, $detected_plugin );
		$product_data['current_seo_title']       = $current_meta['title'];
		$product_data['current_seo_description'] = $current_meta['description'];

		$model = isset( $options['model'] ) && ! empty( $options['model'] )
			? $options['model']
			: \get_option( 'aipt_model', 'gpt-4.1-mini' );

		$generate_focus_kw = ! empty( $prompt_options['generateFocusKeyword'] );
		$optimize_for_kw   = ! empty( $prompt_options['optimizeForFocusKeyword'] );

		if ( $generate_focus_kw && $optimize_for_kw ) {

			$kw_system_prompt = $this->prompt_factory->buildKeywordOnlySystemPrompt( $language, $prompt_options );
			$kw_user_prompt   = $this->prompt_factory->buildKeywordOnlyUserPrompt( $product_data, $prompt_options );
			$kw_response      = $client->generate_description( $kw_system_prompt, $kw_user_prompt, $model );
			$kw_parsed        = $this->parse_response( $kw_response );

			$focus_keyword = isset( $kw_parsed['focus_keyword'] ) ? trim( $kw_parsed['focus_keyword'] ) : '';

			if ( empty( $focus_keyword ) ) {

				return $this->process_product_seo_single_phase( $product_id, $product_data, $current_meta, $prompt_options, $client, $language, $model );
			}

			$meta_system_prompt = $this->prompt_factory->buildOptimizedMetaSystemPrompt( $language, $focus_keyword, $prompt_options );
			$meta_user_prompt   = $this->prompt_factory->buildOptimizedMetaUserPrompt( $product_data, $focus_keyword, $prompt_options );
			$meta_response      = $client->generate_description( $meta_system_prompt, $meta_user_prompt, $model );
			$parsed             = $this->parse_response( $meta_response );

			$parsed['focus_keyword'] = $focus_keyword;
		} else {

			$system_prompt = $this->prompt_factory->buildSystemPrompt( $language, $prompt_options );
			$user_prompt   = $this->prompt_factory->buildUserPrompt( $product_data, $prompt_options );
			$response      = $client->generate_description( $system_prompt, $user_prompt, $model );
			$parsed        = $this->parse_response( $response );
		}

		return $this->build_seo_result( $product_id, $product_data, $current_meta, $parsed );
	}

	private function process_product_seo_single_phase( $product_id, $product_data, $current_meta, $prompt_options, $client, $language, $model ) {
		$system_prompt = $this->prompt_factory->buildSystemPrompt( $language, $prompt_options );
		$user_prompt   = $this->prompt_factory->buildUserPrompt( $product_data, $prompt_options );
		$response      = $client->generate_description( $system_prompt, $user_prompt, $model );
		$parsed        = $this->parse_response( $response );

		return $this->build_seo_result( $product_id, $product_data, $current_meta, $parsed );
	}

	/**
	 * Process SEO meta variant generation for a single product.
	 *
	 * @param int    $product_id Product ID.
	 * @param object $product WooCommerce product object.
	 * @param array  $detected_plugin Detected SEO plugin info.
	 * @param array  $prompt_options Prompt generation options.
	 * @param object $client AI client instance.
	 * @param string $language Target language.
	 * @param array  $options User-provided options.
	 * @return array Result array with variants or error.
	 */
	private function process_product_seo_variants( $product_id, $product, $detected_plugin, $prompt_options, $client, $language, $options ) {
		$product_data = ProductDataFormatter::getFormattedProductData( $product );

		$current_meta              = $this->get_current_seo_meta( $product_id, $detected_plugin );
		$product_data['current_seo_title']       = $current_meta['title'];
		$product_data['current_seo_description'] = $current_meta['description'];

		$model = isset( $options['model'] ) && ! empty( $options['model'] )
			? $options['model']
			: \get_option( 'aipt_model', 'gpt-4.1-mini' );

		$generate_focus_kw = ! empty( $prompt_options['generateFocusKeyword'] );
		$optimize_for_kw   = ! empty( $prompt_options['optimizeForFocusKeyword'] );

		$focus_keyword = '';

		if ( $generate_focus_kw && $optimize_for_kw ) {

			$kw_system_prompt = $this->prompt_factory->buildKeywordOnlySystemPrompt( $language, $prompt_options );
			$kw_user_prompt   = $this->prompt_factory->buildKeywordOnlyUserPrompt( $product_data, $prompt_options );
			$kw_response      = $client->generate_description( $kw_system_prompt, $kw_user_prompt, $model );
			$kw_parsed        = $this->parse_response( $kw_response );

			$focus_keyword = isset( $kw_parsed['focus_keyword'] ) ? trim( $kw_parsed['focus_keyword'] ) : '';

			if ( empty( $focus_keyword ) ) {

				return $this->process_product_seo_variants_single_phase( $product_id, $product_data, $current_meta, $prompt_options, $client, $language, $model );
			}

			$system_prompt = $this->prompt_factory->buildOptimizedVariantSystemPrompt( $language, $focus_keyword, $prompt_options );
			$user_prompt   = $this->prompt_factory->buildOptimizedVariantUserPrompt( $product_data, $focus_keyword, $prompt_options );
		} else {

			$system_prompt = $this->prompt_factory->buildVariantSystemPrompt( $language, $prompt_options );
			$user_prompt   = $this->prompt_factory->buildVariantUserPrompt( $product_data, $prompt_options );
		}

		$response = $client->generate_description( $system_prompt, $user_prompt, $model );
		$parsed_variants = $this->parse_variant_response( $response );

		if ( ! empty( $focus_keyword ) ) {
			foreach ( $parsed_variants as &$variant ) {
				$variant['focus_keyword'] = $focus_keyword;
			}
			unset( $variant );
		}

		$result = array(
			'product_id'   => $product_id,
			'product_name' => $product_data['name'],
			'variants'     => $parsed_variants,
		);

		if ( ! empty( $parsed_variants[0] ) ) {
			$first_variant = $parsed_variants[0];
			$result = $this->build_seo_result( $product_id, $product_data, $current_meta, $first_variant );
			$result['variants'] = $parsed_variants;
		}

		return $result;
	}

	private function build_seo_result( $product_id, $product_data, $current_meta, $parsed ): array {
		$result = array(
			'product_id'   => $product_id,
			'product_name' => $product_data['name'],
			'char_counts'  => array(),
		);

		if ( isset( $parsed['meta_title'] ) ) {
			$result['meta_title']    = $parsed['meta_title'];
			$result['current_title'] = $current_meta['title'];
			$result['char_counts']['title'] = $this->safe_strlen( $parsed['meta_title'] );
		}

		if ( isset( $parsed['meta_description'] ) ) {
			$result['meta_description']    = $parsed['meta_description'];
			$result['current_description'] = $current_meta['description'];
			$result['char_counts']['description'] = $this->safe_strlen( $parsed['meta_description'] );
		}

		if ( isset( $parsed['focus_keyword'] ) ) {
			$result['focus_keyword'] = $parsed['focus_keyword'];
		}

		return $result;
	}

	/**
	 * Single-phase variant generation fallback (used when keyword generation fails).
	 *
	 * @param int    $product_id Product ID.
	 * @param array  $product_data Formatted product data.
	 * @param array  $current_meta Current SEO meta data.
	 * @param array  $prompt_options Prompt generation options.
	 * @param object $client AI client instance.
	 * @param string $language Target language.
	 * @param string $model AI model to use.
	 * @return array Result array with variants.
	 */
	private function process_product_seo_variants_single_phase( $product_id, $product_data, $current_meta, $prompt_options, $client, $language, $model ) {
		$system_prompt = $this->prompt_factory->buildVariantSystemPrompt( $language, $prompt_options );
		$user_prompt   = $this->prompt_factory->buildVariantUserPrompt( $product_data, $prompt_options );
		$response      = $client->generate_description( $system_prompt, $user_prompt, $model );

		$parsed_variants = $this->parse_variant_response( $response );

		$result = array(
			'product_id'   => $product_id,
			'product_name' => $product_data['name'],
			'variants'     => $parsed_variants,
		);

		if ( ! empty( $parsed_variants[0] ) ) {
			$first_variant = $parsed_variants[0];
			if ( isset( $first_variant['meta_title'] ) ) {
				$result['meta_title']    = $first_variant['meta_title'];
				$result['current_title'] = $current_meta['title'];
			}
			if ( isset( $first_variant['meta_description'] ) ) {
				$result['meta_description']    = $first_variant['meta_description'];
				$result['current_description'] = $current_meta['description'];
			}
			if ( isset( $first_variant['focus_keyword'] ) ) {
				$result['focus_keyword'] = $first_variant['focus_keyword'];
			}
			if ( isset( $first_variant['char_counts'] ) ) {
				$result['char_counts'] = $first_variant['char_counts'];
			}
		}

		return $result;
	}

	/**
	 * Safe string length calculation with mbstring fallback.
	 *
	 * @param string $str The string to measure.
	 * @return int The character count.
	 */
	private function safe_strlen( $str ) {
		return function_exists( 'mb_strlen' ) ? mb_strlen( $str, 'UTF-8' ) : strlen( $str );
	}

	private function get_current_seo_meta( $product_id, $detected_plugin ) {
		$title       = '';
		$description = '';

		if ( $detected_plugin && isset( $detected_plugin['meta_keys'] ) ) {
			$meta_keys   = $detected_plugin['meta_keys'];
			$title       = \get_post_meta( $product_id, $meta_keys['title_key'], true );
			$description = \get_post_meta( $product_id, $meta_keys['description_key'], true );
		}

		return array(
			'title'       => $title ?: '',
			'description' => $description ?: '',
		);
	}

	private function parse_response( $response ) {
		if ( empty( $response ) ) {
			return array();
		}

		$json = $this->extract_json( $response );
		if ( $json !== null ) {
			return $json;
		}

		return array();
	}

	/**
	 * Parse variant response from AI client.
	 *
	 * Expects JSON with 'variants' array containing multiple variant objects.
	 * Each variant should have meta_title, meta_description, and optionally focus_keyword.
	 *
	 * @param string $response The AI response text.
	 *
	 * @return array Array of variant objects with index, meta_title, meta_description, focus_keyword, char_counts.
	 */
	private function parse_variant_response( $response ) {
		if ( empty( $response ) ) {
			return array();
		}

		$json = $this->extract_json( $response );
		if ( $json === null || ! isset( $json['variants'] ) || ! is_array( $json['variants'] ) ) {
			return array();
		}

		$variants = array();
		foreach ( $json['variants'] as $index => $variant ) {
			if ( ! is_array( $variant ) ) {
				continue;
			}

			$processed_variant = array(
				'index' => $index,
			);

			$processed_variant['meta_title']       = isset( $variant['meta_title'] ) ? $variant['meta_title'] : '';
			$processed_variant['meta_description'] = isset( $variant['meta_description'] ) ? $variant['meta_description'] : '';
			$processed_variant['focus_keyword']    = isset( $variant['focus_keyword'] ) ? $variant['focus_keyword'] : '';

			$processed_variant['char_counts'] = array(
				'title'       => $this->safe_strlen( $processed_variant['meta_title'] ),
				'description' => $this->safe_strlen( $processed_variant['meta_description'] ),
			);

			$variants[] = $processed_variant;
		}

		return $variants;
	}

	private function extract_json( $text ) {
		$text = trim( $text );

		$decoded = json_decode( $text, true );
		if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
			return $decoded;
		}

		if ( preg_match( '/```(?:json)?\s*([\s\S]*?)```/', $text, $matches ) ) {
			$decoded = json_decode( trim( $matches[1] ), true );
			if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
				return $decoded;
			}
		}

		$json_candidate = $this->extract_json_object_with_key( $text, 'meta_title' );
		if ( $json_candidate !== null ) {
			$decoded = json_decode( $json_candidate, true );
			if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
				return $decoded;
			}
		}

		return null;
	}

	/**
	 * Extract a JSON object containing a specific key using string-aware brace matching.
	 *
	 * This method properly handles braces that appear inside JSON string values,
	 * ensuring accurate extraction even when strings contain { or } characters.
	 *
	 * @param string $text The text to search.
	 * @param string $key  The key that must exist in the JSON object.
	 * @return string|null The JSON string if found, null otherwise.
	 */
	private function extract_json_object_with_key( $text, $key ) {

		$key_pattern = '"' . preg_quote( $key, '/' ) . '"';
		if ( ! preg_match( '/' . $key_pattern . '/', $text, $matches, PREG_OFFSET_CAPTURE ) ) {
			return null;
		}

		$key_pos = $matches[0][1];

		$start     = null;
		$depth     = 0;
		$in_string = false;
		for ( $i = 0; $i <= $key_pos; $i++ ) {
			$char = $text[ $i ];

			if ( $char === '"' ) {
				$backslash_count = 0;
				for ( $j = $i - 1; $j >= 0 && $text[ $j ] === '\\'; $j-- ) {
					$backslash_count++;
				}
				if ( $backslash_count % 2 === 0 ) {
					$in_string = ! $in_string;
				}
				continue;
			}

			if ( $in_string ) {
				continue;
			}

			if ( $char === '{' ) {
				if ( $depth === 0 ) {
					$start = $i;
				}
				$depth++;
			} elseif ( $char === '}' ) {
				$depth--;
			}
		}

		if ( $start === null ) {
			return null;
		}

		$depth     = 0;
		$in_string = false;
		$length    = strlen( $text );
		for ( $i = $start; $i < $length; $i++ ) {
			$char = $text[ $i ];

			if ( $char === '"' ) {
				$backslash_count = 0;
				for ( $j = $i - 1; $j >= 0 && $text[ $j ] === '\\'; $j-- ) {
					$backslash_count++;
				}

				if ( $backslash_count % 2 === 0 ) {
					$in_string = ! $in_string;
				}
				continue;
			}

			if ( $in_string ) {
				continue;
			}

			if ( $char === '{' ) {
				$depth++;
			} elseif ( $char === '}' ) {
				$depth--;
				if ( $depth === 0 ) {
					return substr( $text, $start, $i - $start + 1 );
				}
			}
		}

		return null;
	}
}
