<?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 Wisewand\Logging\Logger;

// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Dynamic SQL filters are built with prepared statements and safe interpolation

/**
 * Smart URL to WordPress Post Matcher
 *
 * Matches ranking URLs from SEO analysis to actual WordPress posts
 * with high accuracy using multiple matching strategies.
 *
 * Features:
 * - Multi-strategy matching (slug, ID, exact URL)
 * - WPML/multilingual support
 * - Confidence scoring
 * - Category extraction
 *
 * @since 1.0.0
 */
class URL_Post_Matcher {

	private $logger;
	private $wpml_enabled;

	public function __construct( Logger $logger ) {
		$this->logger       = $logger;
		$this->wpml_enabled = defined( 'ICL_LANGUAGE_CODE' ) && function_exists( 'wpml_get_language_information' );
	}

	/**
	 * Match a URL to a WordPress post and get its categories
	 *
	 * @param string      $url The URL to match
	 * @param string|null $language_code Expected language code (e.g., 'fr', 'en')
	 * @return array|false Match result with post_id, categories, confidence, or false if no match
	 */
	public function match_url_to_post( $url, $language_code = null ) {
		if ( empty( $url ) ) {
			return false;
		}

		$this->logger->debug(
			'URL Post Matching: Starting',
			array(
				'url'          => $url,
				'language'     => $language_code,
				'wpml_enabled' => $this->wpml_enabled,
			)
		);

		// Normalize URL
		$url = $this->normalize_url( $url );

		// Try multiple matching strategies (ordered by precision)
		$strategies = array(
			'exact_guid'         => array( $this, 'match_by_exact_guid' ),
			'post_name_slug'     => array( $this, 'match_by_post_name' ),
			'post_id_extraction' => array( $this, 'match_by_post_id_in_url' ),
			'fuzzy_path'         => array( $this, 'match_by_fuzzy_path' ),
		);

		foreach ( $strategies as $strategy_name => $strategy_callback ) {
			$match = call_user_func( $strategy_callback, $url, $language_code );

			if ( $match ) {
				$this->logger->info(
					'URL Post Match: Success',
					array(
						'strategy'         => $strategy_name,
						'url'              => $url,
						'post_id'          => $match['post_id'],
						'confidence'       => $match['confidence'],
						'categories_count' => count( $match['categories'] ),
					)
				);

				return $match;
			}
		}

		$this->logger->debug( 'URL Post Match: No match found', array( 'url' => $url ) );
		return false;
	}

	/**
	 * Strategy 1: Exact GUID match
	 * Most reliable for posts with standard WordPress URLs
	 */
	private function match_by_exact_guid( $url, $language_code ) {
		global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query by GUID field more efficient than get_posts() for URL matching
		$post = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT ID, post_type, post_status FROM {$wpdb->posts}
             WHERE guid = %s
             AND post_status IN ('publish', 'draft', 'pending', 'future')
             AND post_type IN ('post', 'page')
             LIMIT 1",
				$url
			)
		);

		if ( ! $post ) {
			return false;
		}

		// Verify language if WPML is active
		if ( $this->wpml_enabled && $language_code ) {
			if ( ! $this->verify_post_language( $post->ID, $language_code ) ) {
				return false;
			}
		}

		return $this->build_match_result( $post->ID, 1.00, 'exact_guid' );
	}

	/**
	 * Strategy 2: Match by post slug (post_name)
	 * Extracts slug from URL and matches against post_name
	 */
	private function match_by_post_name( $url, $language_code ) {
		global $wpdb;

		$slug = $this->extract_slug_from_url( $url );

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

		// Build language filter for WPML
		$language_filter = '';
		if ( $this->wpml_enabled && $language_code ) {
			$language_filter = $wpdb->prepare(
				" AND p.ID IN (
                    SELECT element_id FROM {$wpdb->prefix}icl_translations
                    WHERE element_type LIKE %s
                    AND language_code = %s
                )",
				'post_%',
				$language_code
			);
		}

        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- $language_filter is already prepared, direct query by post_name with WPML filtering more efficient than get_posts()
		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query constructed with prepared components
		$post = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT ID, post_type, post_status FROM {$wpdb->posts} p
             WHERE post_name = %s
             AND post_status IN ('publish', 'draft', 'pending', 'future')
             AND post_type IN ('post', 'page')
             {$language_filter}
             ORDER BY
                CASE post_status
                    WHEN 'publish' THEN 1
                    WHEN 'future' THEN 2
                    WHEN 'pending' THEN 3
                    WHEN 'draft' THEN 4
                END
             LIMIT 1",
				$slug
			)
		);

		if ( ! $post ) {
			return false;
		}

		return $this->build_match_result( $post->ID, 0.95, 'post_name_slug' );
	}

	/**
	 * Strategy 3: Extract post ID from URL parameters or path
	 * Handles URLs like ?p=123 or /archives/123/
	 */
	private function match_by_post_id_in_url( $url, $language_code ) {
		global $wpdb;

		$post_id = null;

		// Check for ?p=123 or &p=123
		if ( preg_match( '/[?&]p=(\d+)/', $url, $matches ) ) {
			$post_id = absint( $matches[1] );
		}
		// Check for /archives/123/ or /123/
		elseif ( preg_match( '/\/(?:archives\/)?(\d+)\/?/', $url, $matches ) ) {
			$post_id = absint( $matches[1] );
		}

		if ( ! $post_id ) {
			return false;
		}

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query by ID with status validation more efficient than get_post()
		$post = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT ID, post_type, post_status FROM {$wpdb->posts}
             WHERE ID = %d
             AND post_status IN ('publish', 'draft', 'pending', 'future')
             AND post_type IN ('post', 'page')
             LIMIT 1",
				$post_id
			)
		);

		if ( ! $post ) {
			return false;
		}

		// Verify language if WPML is active
		if ( $this->wpml_enabled && $language_code ) {
			if ( ! $this->verify_post_language( $post->ID, $language_code ) ) {
				return false;
			}
		}

		return $this->build_match_result( $post->ID, 0.90, 'post_id_extraction' );
	}

	/**
	 * Strategy 4: Fuzzy path matching
	 * Compares URL path segments with post permalinks
	 */
	private function match_by_fuzzy_path( $url, $language_code ) {
		global $wpdb;

		$slug = $this->extract_slug_from_url( $url );

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

		// Build language filter for WPML
		$language_filter = '';
		if ( $this->wpml_enabled && $language_code ) {
			$language_filter = $wpdb->prepare(
				" AND p.ID IN (
                    SELECT element_id FROM {$wpdb->prefix}icl_translations
                    WHERE element_type LIKE %s
                    AND language_code = %s
                )",
				'post_%',
				$language_code
			);
		}

		// Find posts with similar slugs (Levenshtein-like approach)
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- $language_filter is already prepared, LIKE pattern matching with WPML filtering more efficient than get_posts()
		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query constructed with prepared components
		$posts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID, post_name, post_type, post_status FROM {$wpdb->posts} p
             WHERE post_name LIKE %s
             AND post_status IN ('publish', 'draft', 'pending', 'future')
             AND post_type IN ('post', 'page')
             {$language_filter}
             LIMIT 10",
				'%' . $wpdb->esc_like( $slug ) . '%'
			)
		);

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

		// Calculate similarity scores
		$best_match      = null;
		$best_similarity = 0;

		foreach ( $posts as $post ) {
			$similarity = $this->calculate_string_similarity( $slug, $post->post_name );

			if ( $similarity > $best_similarity && $similarity >= 0.85 ) {
				$best_similarity = $similarity;
				$best_match      = $post;
			}
		}

		if ( ! $best_match ) {
			return false;
		}

		// Confidence is proportional to similarity (85-100% → 0.70-0.85)
		$confidence = 0.70 + ( $best_similarity - 0.85 ) * 0.15 / 0.15;

		return $this->build_match_result( $best_match->ID, $confidence, 'fuzzy_path' );
	}

	/**
	 * Build standardized match result with categories
	 */
	private function build_match_result( $post_id, $confidence, $strategy ) {
		$categories = $this->get_post_categories( $post_id );

		return array(
			'post_id'      => (int) $post_id,
			'categories'   => $categories,
			'category_ids' => wp_list_pluck( $categories, 'term_id' ),
			'confidence'   => round( $confidence, 2 ),
			'strategy'     => $strategy,
			'matched_at'   => current_time( 'mysql' ),
		);
	}

	/**
	 * Get all categories for a post
	 */
	private function get_post_categories( $post_id ) {
		$categories = get_the_terms( $post_id, 'category' );

		if ( is_wp_error( $categories ) || empty( $categories ) ) {
			return array();
		}

		return array_map(
			function ( $cat ) {
				return array(
					'term_id' => $cat->term_id,
					'name'    => $cat->name,
					'slug'    => $cat->slug,
					'parent'  => $cat->parent,
				);
			},
			$categories
		);
	}

	/**
	 * Verify post language matches expected language (WPML support)
	 */
	private function verify_post_language( $post_id, $expected_language ) {
		if ( ! $this->wpml_enabled ) {
			return true;
		}

		// Using WPML hook to get post language (hook provided by WPML plugin, not created by us)
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- External WPML hook
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- External WPML hook provided by WPML plugin, cannot be prefixed by theme/plugin
		$post_language = apply_filters(
			'wpml_element_language_code',
			null,
			array(
				'element_id'   => $post_id,
				'element_type' => 'post',
			)
		);

		return $post_language === $expected_language;
	}

	/**
	 * Normalize URL for consistent matching
	 */
	private function normalize_url( $url ) {
		// Remove protocol
		$url = preg_replace( '#^https?://#i', '', $url );

		// Remove www
		$url = preg_replace( '#^www\.#i', '', $url );

		// Remove trailing slash
		$url = rtrim( $url, '/' );

		// Remove query parameters and fragments (keep only path)
		$url = preg_replace( '#[?#].*$#', '', $url );

		return $url;
	}

	/**
	 * Extract slug from URL path (last segment)
	 */
	private function extract_slug_from_url( $url ) {
		// Parse URL path
		$path = wp_parse_url( $url, PHP_URL_PATH );

		if ( empty( $path ) ) {
			return '';
		}

		// Remove leading/trailing slashes
		$path = trim( $path, '/' );

		// Get last segment (the post slug)
		$segments = explode( '/', $path );
		$slug     = end( $segments );

		// Clean slug
		$slug = sanitize_title( $slug );

		return $slug;
	}

	/**
	 * Calculate string similarity (0.00 - 1.00)
	 * Uses similar_text with normalization
	 */
	private function calculate_string_similarity( $string1, $string2 ) {
		if ( $string1 === $string2 ) {
			return 1.00;
		}

		// Normalize strings
		$string1 = strtolower( trim( $string1 ) );
		$string2 = strtolower( trim( $string2 ) );

		if ( empty( $string1 ) || empty( $string2 ) ) {
			return 0.00;
		}

		// Use similar_text for fuzzy matching
		$similarity_percent = 0;
		similar_text( $string1, $string2, $similarity_percent );

		return $similarity_percent / 100;
	}

	/**
	 * Batch match multiple URLs to posts
	 *
	 * @param array       $urls Array of URLs to match
	 * @param string|null $language_code Expected language code
	 * @return array Array of match results indexed by URL
	 */
	public function batch_match_urls( $urls, $language_code = null ) {
		$results = array();

		foreach ( $urls as $url ) {
			$match           = $this->match_url_to_post( $url, $language_code );
			$results[ $url ] = $match ?: null;
		}

		$this->logger->info(
			'URL Post Batch Matching: Complete',
			array(
				'total_urls' => count( $urls ),
				'matched'    => count( array_filter( $results ) ),
				'unmatched'  => count( $urls ) - count( array_filter( $results ) ),
			)
		);

		return $results;
	}
}
