<?php
/**
 * SEO Competitor Handler
 *
 * Handles all AJAX requests related to competitor analysis: spying on
 * competitor keywords via DataForSEO, saving spy keywords to the library,
 * and persisting user column preferences. Extracted from
 * SEO_Intelligence_Real for single-responsibility organisation.
 *
 * @package Wisewand
 * @since 2.8.2
 * @license GPL-2.0-or-later
 */

namespace Wisewand\Admin\SEO;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Wisewand\Admin\Traits\AJAX_Security;

// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- error_log() used within WP_DEBUG conditionals; all AJAX handlers verify nonce before processing $_POST data; direct database calls required for custom plugin table operations

/**
 * Handles SEO competitor analysis AJAX requests
 *
 * Runs DataForSEO-backed competitor keyword spy analysis, saves the
 * discovered keywords to the library, and stores per-user column
 * display preferences. Depends on SEO_Handler_Context for shared services.
 *
 * @package Wisewand
 * @since 2.8.2
 */
class SEO_Competitor_Handler {

	use AJAX_Security;

	/**
	 * Shared handler context (dependencies)
	 *
	 * @since 2.8.2
	 * @var SEO_Handler_Context
	 */
	private $context;

	/**
	 * Create a new competitor handler
	 *
	 * @since 2.8.2
	 *
	 * @param SEO_Handler_Context $context Shared dependencies DTO.
	 */
	public function __construct( SEO_Handler_Context $context ) {
		$this->context = $context;
	}

	// =========================================================================
	// PUBLIC AJAX HANDLERS
	// =========================================================================

	/**
	 * Spy on competitor keyword rankings
	 *
	 * Accepts a list of competitor URLs, runs them through the Competitor_Spy
	 * utility against the DataForSEO API, and returns ranked keyword data with
	 * progress steps.
	 *
	 * @since 2.8.2
	 */
	public function ajax_spy_competitors() {
		$this->verify_ajax_request( 'wisewand_seo_real' );

		// Decode JSON first, then sanitize array elements.
		$raw_competitors = isset( $_POST['competitors'] ) ? wp_unslash( $_POST['competitors'] ) : '[]';
		$competitors_raw = json_decode( $raw_competitors, true );
		if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $competitors_raw ) ) {
			$competitors = array();
		} else {
			// Sanitize each competitor URL.
			$competitors = array_map(
				function ( $competitor ) {
					if ( is_string( $competitor ) ) {
						return esc_url_raw( $competitor );
					}
					return '';
				},
				$competitors_raw
			);
			// Remove empty values.
			$competitors = array_filter( $competitors );
		}

		$analysis_type = sanitize_text_field( $_POST['analysis_type'] ?? 'hybrid' );
		$language_code = sanitize_text_field( $_POST['language_code'] ?? 'en' );
		$location_code = absint( $_POST['location_code'] ?? 2840 );

		// Decode JSON first, then sanitize filter values.
		$raw_filters = isset( $_POST['filters'] ) ? wp_unslash( $_POST['filters'] ) : '{}';
		$filters_raw = json_decode( $raw_filters, true );
		if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $filters_raw ) ) {
			$filters = array();
		} else {
			$filters = array();
			foreach ( $filters_raw as $key => $value ) {
				$sanitized_key = sanitize_key( $key );
				if ( is_numeric( $value ) ) {
					$filters[ $sanitized_key ] = floatval( $value );
				} else {
					$filters[ $sanitized_key ] = sanitize_text_field( $value );
				}
			}
		}

		$user_domain = sanitize_text_field( $_POST['user_domain'] ?? $this->context->domain );

		$this->context->logger->info(
			'Competitor spy AJAX called',
			array(
				'competitors'           => $competitors,
				'analysis_type'         => $analysis_type,
				'user_domain'           => $user_domain,
				'has_dataforseo_client' => ! empty( $this->context->dataforseo_client ),
			)
		);

		if ( empty( $competitors ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'No competitors provided', 'wisewand-ai-content-writer-seo-content-generator' ) ) );
		}

		if ( ! $this->context->dataforseo_client ) {
			wp_send_json_error( array( 'message' => esc_html__( 'DataForSEO not configured. Please add credentials in Settings.', 'wisewand-ai-content-writer-seo-content-generator' ) ) );
		}

		try {
			if ( ! class_exists( 'Wisewand\\Utils\\Competitor_Spy' ) ) {
				require_once WISEWAND_PLUGIN_DIR . 'includes/Utils/Competitor_Spy.php';
			}

			$this->context->logger->info(
				'Creating Competitor_Spy instance',
				array(
					'user_domain' => $user_domain,
					'competitors' => $competitors,
				)
			);

			$spy = new \Wisewand\Utils\Competitor_Spy(
				$this->context->options,
				$this->context->logger,
				$this->context->dataforseo_client,
				$user_domain,
				$this->context->competitor_repository
			);

			$this->context->logger->info(
				'Starting competitor analysis',
				array(
					'analysis_type' => $analysis_type,
					'filters'       => $filters,
				)
			);

			$result = $spy->analyze_competitors( $competitors, $analysis_type, $filters, $language_code, $location_code );

			if ( is_wp_error( $result ) ) {
				$this->context->logger->error(
					'Competitor analysis returned error',
					array(
						'error_code'    => $result->get_error_code(),
						'error_message' => $result->get_error_message(),
					)
				);
				wp_send_json_error( array( 'message' => $result->get_error_message() ) );
				return;
			}

			$this->context->logger->info(
				'Competitor analysis completed',
				array(
					'total_found'    => $result['total_found'],
					'total_returned' => $result['total_returned'],
					'progress_steps' => count( $result['progress_steps'] ?? array() ),
				)
			);

			$response = array(
				'completed' => true,
				'results'   => $result,
				'progress'  => array(
					'percent'      => 100,
					'text'         => 'Completed (' . count( $result['keywords'] ) . ' keywords)',
					'current_task' => 'Analysis Complete',
					'detail'       => "Found {$result['total_found']} keywords, ranked top {$result['total_returned']} by priority",
				),
				'steps'     => $result['progress_steps'] ?? array(),
			);

			wp_send_json_success( $response );

		} catch ( \Exception $e ) {
			$this->context->logger->error(
				'Competitor spy exception',
				array(
					'error' => $e->getMessage(),
					'file'  => $e->getFile(),
					'line'  => $e->getLine(),
					'trace' => $e->getTraceAsString(),
				)
			);
			wp_send_json_error( array( 'message' => esc_html( 'Exception: ' . $e->getMessage() . ' (Line: ' . $e->getLine() . ')' ) ) );
		} catch ( \Error $e ) {
			$this->context->logger->error(
				'Competitor spy fatal error',
				array(
					'error' => $e->getMessage(),
					'file'  => $e->getFile(),
					'line'  => $e->getLine(),
				)
			);
			wp_send_json_error( array( 'message' => esc_html( 'Fatal error: ' . $e->getMessage() ) ) );
		}
	}

	/**
	 * Save spy-discovered keywords to the saved keywords library
	 *
	 * Creates or updates entries in the keyword library for each provided
	 * keyword, attaching enrichment data (volume, difficulty, intent, etc.)
	 * and tagging them as coming from a competitor spy source.
	 *
	 * @since 2.8.2
	 */
	public function ajax_save_spy_keywords() {
		$this->verify_ajax_request( 'wisewand_seo_real' );

		// Sanitize JSON input after decode.
		$keywords_raw = json_decode( wp_unslash( $_POST['keywords'] ?? '[]' ), true );
		$keywords     = array();

		if ( is_array( $keywords_raw ) ) {
			foreach ( $keywords_raw as $kw ) {
				if ( is_array( $kw ) ) {
					$keywords[] = array(
						'keyword' => isset( $kw['keyword'] ) ? sanitize_text_field( $kw['keyword'] ) : '',
						'volume'  => isset( $kw['volume'] ) ? absint( $kw['volume'] ) : 0,
					);
				}
			}
		}

		$analysis_id   = sanitize_text_field( wp_unslash( $_POST['analysis_id'] ?? '' ) );
		$language_code = sanitize_text_field( $_POST['language_code'] ?? 'en' );
		$location_code = absint( $_POST['location_code'] ?? 2840 );

		if ( empty( $keywords ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'No keywords provided', 'wisewand-ai-content-writer-seo-content-generator' ) ) );
		}

		try {
			$saved_manager = $this->context->get_saved_keywords_manager();

			$saved   = 0;
			$skipped = 0;

			foreach ( $keywords as $kw ) {
				$keyword = $kw['keyword'] ?? '';
				if ( empty( $keyword ) ) {
					continue;
				}

				// CRITICAL: Check ALL 3 fields that match database UNIQUE constraint
				// UNIQUE KEY keyword_language (keyword, language_code, location_code).
				$existing_id = $saved_manager->get_keyword_id( $keyword, $language_code, $location_code );

				if ( $existing_id ) {
					++$skipped;
					$this->context->logger->info(
						'Keyword already exists, updating instead',
						array(
							'keyword'     => $keyword,
							'existing_id' => $existing_id,
						)
					);

					$this->context->keyword_repository->update_keyword(
						$existing_id,
						array(
							'keyword_source' => 'competitor_spy',
							'language_code'  => $language_code,
							'location_code'  => $location_code,
							'search_volume'  => $kw['search_volume'] ?? 0,
							'difficulty'     => $kw['difficulty'] ?? null,
							'cpc'            => $kw['cpc'] ?? null,
							'competition'    => $kw['competition'] ?? null,
							'search_intent'  => $kw['search_intent'] ?? 'informational',
							'priority_score' => $kw['priority_score'] ?? 0,
							'is_enriched'    => 1,
							'tags'           => 'competitor_spy',
							'notes'          => 'From: ' . ( $kw['competitor_domain'] ?? 'competitor' ) . ', Pos: ' . ( $kw['competitor_position'] ?? 'N/A' ),
						)
					);
					continue;
				}

				$keyword_id = $saved_manager->save_keyword( $keyword, 'competitor_spy', $kw['competitor_domain'] ?? null );

				if ( ! is_wp_error( $keyword_id ) ) {
					$update_result = $this->context->keyword_repository->update_keyword(
						$keyword_id,
						array(
							'language_code'  => $language_code,
							'location_code'  => $location_code,
							'search_volume'  => $kw['search_volume'] ?? 0,
							'difficulty'     => $kw['difficulty'] ?? null,
							'cpc'            => $kw['cpc'] ?? null,
							'competition'    => $kw['competition'] ?? null,
							'search_intent'  => $kw['search_intent'] ?? 'informational',
							'priority_score' => $kw['priority_score'] ?? 0,
							'is_enriched'    => 1,
							'tags'           => 'competitor_spy',
							'notes'          => 'From: ' . ( $kw['competitor_domain'] ?? 'competitor' ) . ', Pos: ' . ( $kw['competitor_position'] ?? 'N/A' ),
						)
					);

					if ( $update_result !== false ) {
						++$saved;
						$this->context->logger->info(
							'Competitor keyword saved and enriched',
							array(
								'keyword_id'  => $keyword_id,
								'keyword'     => $keyword,
								'volume'      => $kw['search_volume'] ?? 0,
								'is_enriched' => 1,
							)
						);
					} else {
						$this->context->logger->error(
							'Failed to update keyword',
							array(
								'keyword_id' => $keyword_id,
								'keyword'    => $keyword,
								'db_error'   => $this->context->keyword_repository->get_last_error(),
							)
						);
					}
				}
			}

			if ( $analysis_id ) {
				$this->context->competitor_repository->update_analysis(
					$analysis_id,
					array( 'keywords_saved' => $saved )
				);
			}

			wp_send_json_success(
				array(
					'saved'   => $saved,
					'skipped' => $skipped,
					'total'   => count( $keywords ),
				)
			);

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => esc_html( $e->getMessage() ) ) );
		}
	}

	/**
	 * Persist the user's column visibility preferences for the SEO table
	 *
	 * Stores a serialised preferences string in user meta so the chosen
	 * column set is remembered across page loads.
	 *
	 * @since 2.8.2
	 */
	public function ajax_save_column_preferences() {
		$this->verify_ajax_request( 'wisewand_seo_real' );

		$preferences = sanitize_text_field( $_POST['preferences'] ?? '' );

		if ( $preferences ) {
			update_user_meta( get_current_user_id(), 'wisewand_seo_table_columns', $preferences );
			wp_send_json_success( array( 'message' => 'Preferences saved' ) );
		} else {
			wp_send_json_error( array( 'message' => 'Invalid preferences' ) );
		}
	}

	// =========================================================================
	// PRIVATE HELPERS
	// =========================================================================

	/**
	 * Return the Wisewand app URL for a given content type
	 *
	 * Maps a content type slug to the matching app.wisewand.ai URL so that
	 * competitor keyword table rows can link to the correct content creator.
	 *
	 * @since 2.8.2
	 *
	 * @param string $content_type Content type slug (blog|affiliate|category|product).
	 * @return string Full URL string.
	 */
	private function get_wisewand_url( $content_type ) {
		$urls = array(
			'blog'      => 'https://app.wisewand.ai/articles/blog',
			'affiliate' => 'https://app.wisewand.ai/articles/affiliation',
			'category'  => 'https://app.wisewand.ai/e-commerce/category-page',
			'product'   => 'https://app.wisewand.ai/e-commerce/product-page',
		);
		return $urls[ $content_type ] ?? $urls['blog'];
	}
}
