<?php
/**
 * Advanced Duplicate Detection Service.
 *
 * @package PrimeLink
 */

namespace PrimeLink\Modules\Imports\Service;

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

final class AdvancedDuplicateDetectionService {

    /**
     * Find existing product using advanced matching strategies.
     *
     * @param array $mapped Mapped product data.
     * @param array $config Matching configuration.
     * @return int|array Product ID or array of potential matches with scores.
     */
    public static function find_existing_product( $mapped, $config = array() ) {
        $defaults = array(
            'strategy'        => 'single', // 'single', 'multiple', 'fuzzy', 'hybrid'
            'fields'          => array( 'sku' ), // Fields to match on
            'fuzzy_threshold' => 0.85, // Similarity threshold (0-1)
            'require_all'     => false, // For multiple: require all fields to match
            'return_matches'  => false, // Return all potential matches with scores
        );
        $config = wp_parse_args( $config, $defaults );

        $strategy = $config['strategy'];
        $fields   = (array) $config['fields'];

        switch ( $strategy ) {
            case 'single':
                return self::match_single( $mapped, $fields[0] ?? 'sku' );

            case 'multiple':
                return self::match_multiple( $mapped, $fields, $config['require_all'] );

            case 'fuzzy':
                return self::match_fuzzy( $mapped, $fields, $config['fuzzy_threshold'], $config['return_matches'] );

            case 'hybrid':
                return self::match_hybrid( $mapped, $fields, $config['fuzzy_threshold'], $config['return_matches'] );

            default:
                return 0;
        }
    }

    /**
     * Single field matching (existing simple logic).
     *
     * @param array  $mapped Mapped product data.
     * @param string $field  Field to match on.
     * @return int Product ID or 0.
     */
    private static function match_single( $mapped, $field ) {
        return ImportRunner::find_existing_product_id( $mapped, $field );
    }

    /**
     * Multiple fields matching (AND/OR logic).
     *
     * @param array $mapped      Mapped product data.
     * @param array $fields      Fields to match on.
     * @param bool  $require_all If true, all fields must match (AND). If false, any field can match (OR).
     * @return int Product ID or 0.
     */
    private static function match_multiple( $mapped, $fields, $require_all = false ) {
        if ( empty( $fields ) ) {
            return 0;
        }

        $matches = array();

        foreach ( $fields as $field ) {
            $id = self::match_single( $mapped, $field );
            if ( $id > 0 ) {
                if ( ! $require_all ) {
                    // OR logic: return first match
                    return $id;
                }
                $matches[] = $id;
            } elseif ( $require_all ) {
                // AND logic: one field doesn't match, return 0
                return 0;
            }
        }

        if ( $require_all && ! empty( $matches ) ) {
            // All fields matched - return the most common ID (or first if all same)
            $counts = array_count_values( $matches );
            arsort( $counts );
            return absint( array_key_first( $counts ) );
        }

        return 0;
    }

    /**
     * Fuzzy matching using similarity algorithms.
     *
     * @param array $mapped         Mapped product data.
     * @param array $fields         Fields to match on.
     * @param float $threshold      Similarity threshold (0-1).
     * @param bool  $return_matches Return all matches with scores.
     * @return int|array Product ID or array of matches.
     */
    private static function match_fuzzy( $mapped, $fields, $threshold = 0.85, $return_matches = false ) {
        $matches = array();

        foreach ( $fields as $field ) {
            $value = self::get_field_value( $mapped, $field );
            if ( empty( $value ) ) {
                continue;
            }

            $field_matches = self::find_similar_products( $value, $field, $threshold );
            foreach ( $field_matches as $product_id => $score ) {
                if ( ! isset( $matches[ $product_id ] ) ) {
                    $matches[ $product_id ] = 0;
                }
                $matches[ $product_id ] = max( $matches[ $product_id ], $score );
            }
        }

        if ( empty( $matches ) ) {
            return $return_matches ? array() : 0;
        }

        // Sort by score descending
        arsort( $matches );

        if ( $return_matches ) {
            return $matches;
        }

        // Return best match if above threshold
        $best_id    = array_key_first( $matches );
        $best_score = $matches[ $best_id ];

        return $best_score >= $threshold ? absint( $best_id ) : 0;
    }

    /**
     * Hybrid matching: try exact first, then fuzzy.
     *
     * @param array $mapped         Mapped product data.
     * @param array $fields         Fields to match on.
     * @param float $threshold      Similarity threshold.
     * @param bool  $return_matches Return all matches with scores.
     * @return int|array Product ID or array of matches.
     */
    private static function match_hybrid( $mapped, $fields, $threshold = 0.85, $return_matches = false ) {
        // First try exact matching
        $exact = self::match_multiple( $mapped, $fields, false );
        if ( $exact > 0 ) {
            return $return_matches ? array( $exact => 1.0 ) : $exact;
        }

        // Fallback to fuzzy
        return self::match_fuzzy( $mapped, $fields, $threshold, $return_matches );
    }

    /**
     * Get field value from mapped data.
     *
     * @param array  $mapped Mapped product data.
     * @param string $field  Field name.
     * @return string
     */
    private static function get_field_value( $mapped, $field ) {
        switch ( $field ) {
            case 'sku':
                return isset( $mapped['product']['sku'] ) ? (string) $mapped['product']['sku'] : '';

            case 'title':
                return isset( $mapped['post']['post_title'] ) ? (string) $mapped['post']['post_title'] : '';

            case 'slug':
                return isset( $mapped['post']['post_name'] ) ? (string) $mapped['post']['post_name'] : '';

            case 'content':
                return isset( $mapped['post']['post_content'] ) ? (string) $mapped['post']['post_content'] : '';

            default:
                // Custom field or meta
                if ( isset( $mapped['meta'][ $field ] ) ) {
                    return (string) $mapped['meta'][ $field ];
                }
                return '';
        }
    }

    /**
     * Find similar products using fuzzy matching.
     *
     * @param string $value     Value to search for.
     * @param string $field     Field to search in.
     * @param float  $threshold Similarity threshold.
     * @return array Array of product_id => score.
     */
    private static function find_similar_products( $value, $field, $threshold = 0.85 ) {
        $matches = array();
        $value   = trim( strtolower( $value ) );

        if ( empty( $value ) ) {
            return $matches;
        }

        // Get all products
        $args = array(
            'post_type'      => 'product',
            'post_status'    => array( 'publish', 'draft', 'pending', 'private' ),
            'posts_per_page' => -1,
            'fields'         => 'ids',
        );

        $products = get_posts( $args );

        foreach ( $products as $product_id ) {
            $product_value = self::get_product_field_value( $product_id, $field );
            if ( empty( $product_value ) ) {
                continue;
            }

            $product_value = trim( strtolower( $product_value ) );
            $similarity    = self::calculate_similarity( $value, $product_value );

            if ( $similarity >= $threshold ) {
                $matches[ $product_id ] = $similarity;
            }
        }

        return $matches;
    }

    /**
     * Get product field value.
     *
     * @param int    $product_id Product ID.
     * @param string $field      Field name.
     * @return string
     */
    private static function get_product_field_value( $product_id, $field ) {
        switch ( $field ) {
            case 'sku':
                $product = wc_get_product( $product_id );
                return $product ? $product->get_sku() : '';

            case 'title':
                return get_the_title( $product_id );

            case 'slug':
                $post = get_post( $product_id );
                return $post ? $post->post_name : '';

            case 'content':
                $post = get_post( $product_id );
                return $post ? $post->post_content : '';

            default:
                // Custom field or meta
                return get_post_meta( $product_id, $field, true );
        }
    }

    /**
     * Calculate similarity between two strings using multiple algorithms.
     *
     * @param string $str1 First string.
     * @param string $str2 Second string.
     * @return float Similarity score (0-1).
     */
    private static function calculate_similarity( $str1, $str2 ) {
        if ( $str1 === $str2 ) {
            return 1.0;
        }

        // Use Levenshtein distance for similarity
        $max_len = max( strlen( $str1 ), strlen( $str2 ) );
        if ( $max_len === 0 ) {
            return 0.0;
        }

        $distance = levenshtein( $str1, $str2 );
        $similarity = 1 - ( $distance / $max_len );

        // Also check for substring matches (partial similarity boost)
        if ( strpos( $str1, $str2 ) !== false || strpos( $str2, $str1 ) !== false ) {
            $similarity = max( $similarity, 0.7 );
        }

        // Check word overlap
        $words1 = array_filter( explode( ' ', $str1 ) );
        $words2 = array_filter( explode( ' ', $str2 ) );
        if ( ! empty( $words1 ) && ! empty( $words2 ) ) {
            $common = count( array_intersect( $words1, $words2 ) );
            $total  = count( array_unique( array_merge( $words1, $words2 ) ) );
            $word_similarity = $total > 0 ? ( $common / $total ) : 0;
            $similarity = max( $similarity, $word_similarity * 0.9 );
        }

        return min( 1.0, max( 0.0, $similarity ) );
    }

    /**
     * Preview potential duplicates before import.
     *
     * @param array $mapped Mapped product data.
     * @param array $config Matching configuration.
     * @return array Array of potential matches with details.
     */
    public static function preview_duplicates( $mapped, $config = array() ) {
        $matches = self::find_existing_product( $mapped, array_merge( $config, array( 'return_matches' => true ) ) );

        if ( ! is_array( $matches ) ) {
            return array();
        }

        $results = array();
        foreach ( $matches as $product_id => $score ) {
            $product = wc_get_product( $product_id );
            if ( ! $product ) {
                continue;
            }

            $results[] = array(
                'id'      => $product_id,
                'title'   => $product->get_name(),
                'sku'     => $product->get_sku(),
                'score'   => round( $score * 100, 2 ),
                'url'     => get_edit_post_link( $product_id, 'raw' ),
            );
        }

        return $results;
    }
}

