<?php
/**
 * This file is part of the Wisewand WordPress plugin.
 *
 * @package Wisewand
 * @since 1.0.0
 * @license GPL-2.0-or-later
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
namespace Wisewand\Utils;

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

use WP_Error;
use Wisewand\Logging\Logger;

// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names derived from $wpdb->prefix are safe to interpolate

/**
 * Cannibalisation Detector
 *
 * Detects potential keyword cannibalisation before scheduling content
 * Uses moderate-depth detection: database queries + semantic similarity
 */
class Cannibalisation_Detector {

	private $logger;
	private $rate_limiter;

	public function __construct( Logger $logger, $rate_limiter = null ) {
		$this->logger = $logger;
		$this->rate_limiter = $rate_limiter ?? new \Wisewand\Utils\Rate_Limiter( $this->logger );
	}

	/**
	 * Analyze keyword for potential cannibalisation
	 *
	 * @param string $keyword Keyword to check
	 * @param array  $keyword_metrics Optional metrics (position, volume, etc.)
	 * @return array [risk_level, conflicts[], recommended_action, reason]
	 */
	public function analyze_keyword( $keyword, $keyword_metrics = array() ) {
		$conflicts = array();

		// Method 1: Exact title matches
		$title_matches = $this->find_title_matches( $keyword );
		if ( ! empty( $title_matches ) ) {
			$conflicts = array_merge( $conflicts, $title_matches );
		}

		// Method 2: SERP overlap (check if URLs already rank for this keyword)
		$serp_conflicts = $this->find_serp_overlap( $keyword );
		if ( ! empty( $serp_conflicts ) ) {
			$conflicts = array_merge( $conflicts, $serp_conflicts );
		}

		// Method 3: Semantic similarity to existing content
		$similar_content = $this->find_semantically_similar( $keyword );
		if ( ! empty( $similar_content ) ) {
			$conflicts = array_merge( $conflicts, $similar_content );
		}

		// Deduplicate conflicts (same post may match multiple methods)
		$conflicts = $this->deduplicate_conflicts( $conflicts );

		// Calculate risk level
		$risk_level = $this->calculate_risk_level( $conflicts, $keyword_metrics );

		// Recommend action
		$recommendation = $this->recommend_action( $conflicts, $keyword_metrics, $risk_level );

		$this->logger->info(
			'Cannibalisation analysis complete',
			array(
				'keyword'            => $keyword,
				'conflicts_found'    => count( $conflicts ),
				'risk_level'         => $risk_level,
				'recommended_action' => $recommendation['action'],
			)
		);

		return array(
			'keyword'            => $keyword,
			'risk_level'         => $risk_level,
			'conflicts'          => $conflicts,
			'recommended_action' => $recommendation['action'],
			'reason'             => $recommendation['reason'],
			'primary_conflict'   => $conflicts[0] ?? null,
		);
	}

	/**
	 * Find posts with keyword in title
	 */
	private function find_title_matches( $keyword ) {
		global $wpdb;

		$like_keyword = '%' . $wpdb->esc_like( $keyword ) . '%';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query with LIKE pattern matching more efficient than get_posts() with meta_query
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"
            SELECT
                p.ID,
                p.post_title,
                p.post_name,
                p.post_modified,
                p.post_status,
                p.post_type
            FROM {$wpdb->posts} p
            WHERE p.post_title LIKE %s
            AND p.post_status IN ('publish', 'draft', 'pending', 'future')
            AND p.post_type IN ('post', 'page', 'product')
            ORDER BY p.post_modified DESC
            LIMIT 5
        ",
				$like_keyword
			),
			ARRAY_A
		);

		$conflicts = array();
		foreach ( $results as $post ) {
			$conflicts[] = array(
				'post_id'          => $post['ID'],
				'post_title'       => $post['post_title'],
				'post_url'         => get_permalink( $post['ID'] ),
				'detection_method' => 'exact_title',
				'similarity_score' => $this->calculate_title_similarity( $keyword, $post['post_title'] ),
				'post_status'      => $post['post_status'],
			);
		}

		return $conflicts;
	}

	/**
	 * Find URLs that already rank for this keyword
	 */
	private function find_serp_overlap( $keyword ) {
		global $wpdb;

		$keywords_table = $wpdb->prefix . 'wisewand_seo_keywords_v2';

		// Check if table exists
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DDL SHOW TABLES query for table existence check
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $keywords_table ) ) !== $keywords_table ) {
			return array();
		}

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for SERP overlap detection
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"
            SELECT
                keyword,
                page_url,
                position_abs as position,
                search_volume
            FROM {$keywords_table}
            WHERE keyword = %s
            AND position_abs IS NOT NULL
            AND position_abs <= 50
            ORDER BY position_abs ASC
            LIMIT 5
        ",
				$keyword
			),
			ARRAY_A
		);

		$conflicts = array();
		foreach ( $results as $ranking ) {
			$post_id = url_to_postid( $ranking['page_url'] );

			if ( $post_id ) {
				$conflicts[] = array(
					'post_id'          => $post_id,
					'post_title'       => get_the_title( $post_id ),
					'post_url'         => $ranking['page_url'],
					'detection_method' => 'serp_overlap',
					'similarity_score' => 1.0, // Exact keyword match
					'current_position' => $ranking['position'],
					'search_volume'    => $ranking['search_volume'],
				);
			}
		}

		return $conflicts;
	}

	/**
	 * Find semantically similar content
	 */
	private function find_semantically_similar( $keyword ) {
		global $wpdb;

		$keyword_words = $this->extract_significant_words( $keyword );

		if ( count( $keyword_words ) < 2 ) {
			return array(); // Too short to check similarity
		}

		// Build LIKE conditions for each significant word
		$like_conditions = array();
		$like_params     = array();

		foreach ( $keyword_words as $word ) {
			if ( strlen( $word ) > 2 ) {
				$like_conditions[] = 'p.post_title LIKE %s';
				$like_params[]     = '%' . $wpdb->esc_like( $word ) . '%';
			}
		}

		if ( empty( $like_conditions ) ) {
			return array();
		}

		$sql = "
            SELECT
                p.ID,
                p.post_title,
                p.post_name,
                p.post_modified
            FROM {$wpdb->posts} p
            WHERE (" . implode( ' OR ', $like_conditions ) . ")
            AND p.post_status IN ('publish', 'draft', 'pending')
            AND p.post_type IN ('post', 'page')
            ORDER BY p.post_modified DESC
            LIMIT 10
        ";

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Dynamic LIKE conditions for semantic similarity search, more efficient than multiple get_posts() calls
		$results = $wpdb->get_results( $wpdb->prepare( $sql, $like_params ), ARRAY_A );

		$conflicts = array();
		foreach ( $results as $post ) {
			$similarity = $this->calculate_semantic_similarity( $keyword, $post['post_title'] );

			// Only include if similarity is significant (> 0.4)
			if ( $similarity > 0.4 ) {
				$conflicts[] = array(
					'post_id'          => $post['ID'],
					'post_title'       => $post['post_title'],
					'post_url'         => get_permalink( $post['ID'] ),
					'detection_method' => 'semantic_similarity',
					'similarity_score' => $similarity,
				);
			}
		}

		return $conflicts;
	}

	/**
	 * Calculate title similarity (0-1 scale)
	 */
	private function calculate_title_similarity( $keyword, $title ) {
		$kw_lower    = strtolower( $keyword );
		$title_lower = strtolower( $title );

		// Exact match
		if ( $kw_lower === $title_lower ) {
			return 1.0;
		}

		// Contains keyword
		if ( strpos( $title_lower, $kw_lower ) !== false ) {
			return 0.9;
		}

		// Word overlap
		$kw_words    = $this->extract_significant_words( $keyword );
		$title_words = $this->extract_significant_words( $title );

		$intersection = count( array_intersect( $kw_words, $title_words ) );
		$union        = count( array_unique( array_merge( $kw_words, $title_words ) ) );

		return $union > 0 ? $intersection / $union : 0;
	}

	/**
	 * Calculate semantic similarity using Levenshtein distance
	 */
	private function calculate_semantic_similarity( $keyword, $title ) {
		$kw_lower    = strtolower( $keyword );
		$title_lower = strtolower( $title );

		// Quick check - exact or substring match
		if ( strpos( $title_lower, $kw_lower ) !== false ) {
			return 0.95;
		}

		// Word-level similarity (Jaccard)
		$kw_words    = $this->extract_significant_words( $keyword );
		$title_words = $this->extract_significant_words( $title );

		$intersection = count( array_intersect( $kw_words, $title_words ) );
		$union        = count( array_unique( array_merge( $kw_words, $title_words ) ) );

		if ( $union === 0 ) {
			return 0;
		}

		$jaccard_similarity = $intersection / $union;

		// Levenshtein distance (normalized)
		$max_len = max( strlen( $kw_lower ), strlen( $title_lower ) );
		if ( $max_len > 0 && $max_len < 255 ) {
			$lev_distance   = levenshtein( $kw_lower, $title_lower );
			$lev_similarity = 1 - ( $lev_distance / $max_len );

			// Average of both methods
			return ( $jaccard_similarity + $lev_similarity ) / 2;
		}

		return $jaccard_similarity;
	}

	/**
	 * Extract significant words from text
	 */
	private function extract_significant_words( $text ) {
		$words     = preg_split( '/[\s\-_]+/', strtolower( $text ) );
		$stopwords = array( 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from' );

		return array_filter(
			$words,
			function ( $word ) use ( $stopwords ) {
				return strlen( $word ) > 2 && ! in_array( $word, $stopwords ) && ! is_numeric( $word );
			}
		);
	}

	/**
	 * Deduplicate conflicts (same post via different methods)
	 */
	private function deduplicate_conflicts( $conflicts ) {
		$deduplicated  = array();
		$seen_post_ids = array();

		foreach ( $conflicts as $conflict ) {
			$post_id = $conflict['post_id'];

			if ( ! isset( $seen_post_ids[ $post_id ] ) ) {
				$deduplicated[]            = $conflict;
				$seen_post_ids[ $post_id ] = true;
			} elseif ( isset( $conflict['current_position'] ) ) {
				// Keep the one with position data (from SERP overlap)
				foreach ( $deduplicated as $key => $existing ) {
					if ( $existing['post_id'] === $post_id && ! isset( $existing['current_position'] ) ) {
						$deduplicated[ $key ] = $conflict;
						break;
					}
				}
			}
		}

		// Sort by similarity score DESC
		usort(
			$deduplicated,
			function ( $a, $b ) {
				return ( $b['similarity_score'] ?? 0 ) <=> ( $a['similarity_score'] ?? 0 );
			}
		);

		return $deduplicated;
	}

	/**
	 * Calculate risk level based on conflicts
	 */
	private function calculate_risk_level( $conflicts, $keyword_metrics ) {
		if ( empty( $conflicts ) ) {
			return 'none';
		}

		$highest_similarity = $conflicts[0]['similarity_score'] ?? 0;
		$has_serp_overlap   = ! empty( array_filter( $conflicts, fn( $c ) => $c['detection_method'] === 'serp_overlap' ) );

		// HIGH risk
		if ( $highest_similarity >= 0.9 || $has_serp_overlap ) {
			return 'high';
		}

		// MEDIUM risk
		if ( $highest_similarity >= 0.7 || count( $conflicts ) >= 3 ) {
			return 'medium';
		}

		// LOW risk
		if ( $highest_similarity >= 0.5 ) {
			return 'low';
		}

		return 'none';
	}

	/**
	 * Recommend action based on conflicts and metrics
	 */
	private function recommend_action( $conflicts, $keyword_metrics, $risk_level ) {
		if ( 'none' === $risk_level ) {
			return array(
				'action' => 'create',
				'reason' => 'No conflicts detected - safe to create new content',
			);
		}

		$primary_conflict = $conflicts[0] ?? null;

		if ( ! $primary_conflict ) {
			return array(
				'action' => 'create',
				'reason' => 'No significant conflicts',
			);
		}

		// If post already ranks well (position ≤ 10)
		if ( isset( $primary_conflict['current_position'] ) && $primary_conflict['current_position'] <= 10 ) {
			return array(
				'action'         => 'update',
				'reason'         => 'Existing content ranks #' . $primary_conflict['current_position'] . ' - optimize instead of creating new',
				'target_post_id' => $primary_conflict['post_id'],
			);
		}

		// If high similarity match exists
		if ( $primary_conflict['similarity_score'] >= 0.8 ) {
			return array(
				'action'         => 'update',
				'reason'         => 'Existing content very similar (' . round( $primary_conflict['similarity_score'] * 100 ) . '% match) - update recommended',
				'target_post_id' => $primary_conflict['post_id'],
			);
		}

		// If post ranks but not well (position > 10)
		if ( isset( $primary_conflict['current_position'] ) && $primary_conflict['current_position'] > 10 ) {
			return array(
				'action'         => 'update_or_create',
				'reason'         => 'Existing content ranks #' . $primary_conflict['current_position'] . ' - could update OR create better content',
				'target_post_id' => $primary_conflict['post_id'],
			);
		}

		// Medium risk - user should review
		if ( 'medium' === $risk_level ) {
			return array(
				'action'         => 'review',
				'reason'         => 'Moderate similarity detected - review existing content before proceeding',
				'target_post_id' => $primary_conflict['post_id'],
			);
		}

		// Default: create new
		return array(
			'action' => 'create',
			'reason' => 'Low risk - conflicts are minor',
		);
	}

	/**
	 * Batch analyze multiple keywords
	 */
	public function batch_analyze( $keywords ) {
		$results = array();

		foreach ( $keywords as $kw ) {
			$keyword = is_array( $kw ) ? ( $kw['keyword'] ?? '' ) : $kw;

			if ( empty( $keyword ) ) {
				continue;
			}

			$metrics  = is_array( $kw ) ? $kw : array();
			$analysis = $this->analyze_keyword( $keyword, $metrics );

			$results[] = $analysis;

			// Rate limiting for large batches
			if ( count( $results ) % 50 === 0 ) {
				$this->rate_limiter->smart_delay( 'cpu_yield', 0.1 );
			}
		}

		return $results;
	}

	/**
	 * Get summary statistics for batch analysis
	 */
	public function get_analysis_summary( $batch_results ) {
		$summary = array(
			'total_analyzed'      => count( $batch_results ),
			'none_risk'           => 0,
			'low_risk'            => 0,
			'medium_risk'         => 0,
			'high_risk'           => 0,
			'recommended_updates' => 0,
			'recommended_creates' => 0,
			'requires_review'     => 0,
		);

		foreach ( $batch_results as $result ) {
			// Count by risk level
			$risk = $result['risk_level'];
			++$summary[ $risk . '_risk' ];

			// Count by recommended action
			$action = $result['recommended_action'];
			if ( 'update' === $action ) {
				++$summary['recommended_updates'];
			} elseif ( $action === 'create' ) {
				++$summary['recommended_creates'];
			} elseif ( in_array( $action, array( 'review', 'update_or_create' ) ) ) {
				++$summary['requires_review'];
			}
		}

		return $summary;
	}

	/**
	 * Check if keyword is already in editorial calendar
	 */
	public function is_already_scheduled( $keyword ) {
		global $wpdb;

		$calendar_table = $wpdb->prefix . 'wisewand_editorial_calendar';

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table query for checking scheduled keywords
		$exists = $wpdb->get_var(
			$wpdb->prepare(
				"
            SELECT COUNT(*) FROM {$calendar_table}
            WHERE keyword = %s
            AND publication_date >= CURDATE()
            AND wordpress_status NOT IN ('published', 'failed')
        ",
				$keyword
			)
		);

		return $exists > 0;
	}
}
