<?php
/**
 * YITH WooCommerce Wishlist Importer class.
 *
 * @package SaveToWishlist\Classes\Importers
 * @since 1.0.0
 */
namespace SaveToWishlist\Classes\Importers;

use SaveToWishlist\Helpers\Helper_Database;
use SaveToWishlist\Classes\Factories\Collections;
use SaveToWishlist\Classes\Factories\Collection_Item;

/**
 * Class YITH_Importer
 *
 * Imports wishlists from YITH WooCommerce Wishlist.
 *
 * @since 1.0.0
 */
class YITH_Importer extends Abstract_Importer {

    /**
     * YITH items table name.
     */
    const YITH_ITEMS_TABLE = 'yith_wcwl';

    /**
     * YITH lists table name.
     */
    const YITH_LISTS_TABLE = 'yith_wcwl_lists';

    /**
     * YITH plugin files (relative to plugins dir).
     *
     * Lists both the free and Premium variants so either activation flips the
     * importer on.
     */
    const YITH_PLUGIN_FILES = array(
        'yith-woocommerce-wishlist/init.php',
        'yith-woocommerce-wishlist-premium/init.php',
    );

    /**
     * YITH meta table name.
     */
    const YITH_META_TABLE = 'yith_wcwl_itemmeta';

    /**
     * Batch size for processing.
     */
    const BATCH_SIZE = 50;

    /**
     * Get source name.
     *
     * @since 1.0.0
     * @access public
     * @return string
     */
    public function get_source_name(): string {
        return 'YITH WooCommerce Wishlist';
    }

    /**
     * Get source slug.
     *
     * @since 1.0.0
     * @access public
     * @return string
     */
    public function get_source_slug(): string {
        return 'yith';
    }

    /**
     * Check if source is active.
     *
     * Returns true only when the YITH source plugin is installed and active —
     * leftover wishlist tables alone are not enough to surface the importer.
     *
     * @since 1.0.0
     * @access public
     * @return bool
     */
    public function is_source_active(): bool {
        return $this->is_plugin_installed_and_active( self::YITH_PLUGIN_FILES );
    }

    /**
     * Get source version.
     *
     * @since 1.0.0
     * @access public
     * @return string
     */
    public function get_source_version(): string {
        if ( ! function_exists( 'get_plugin_data' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        foreach ( self::YITH_PLUGIN_FILES as $plugin_file ) {
            $absolute_path = WP_PLUGIN_DIR . '/' . $plugin_file;
            if ( file_exists( $absolute_path ) ) {
                $plugin_data = get_plugin_data( $absolute_path );
                return $plugin_data['Version'] ?? 'Unknown';
            }
        }

        return 'Unknown';
    }

    /**
     * Get source data count.
     *
     * @since 1.0.0
     * @access public
     * @return array
     */
    public function get_source_data_count(): array {
        global $wpdb;

        if ( ! $this->is_source_active() ) {
            return array(
                'countFromSource' => 0,
                'wishlists'       => 0,
                'items'           => 0,
                'users'           => 0,
                'guests'          => 0,
            );
        }

        // phpcs:disable

        $lists_table       = $wpdb->prefix . self::YITH_LISTS_TABLE;
        $items_table       = $wpdb->prefix . self::YITH_ITEMS_TABLE;
        $collections_table = Helper_Database::get_collections_table_name();

        // Count wishlists that haven't been imported yet (excluding guest wishlists and already imported ones).
        $wishlists = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) 
                FROM {$lists_table} l
                LEFT JOIN {$collections_table} c ON l.wishlist_token = c.url_code
                WHERE l.user_id IS NOT NULL 
                AND l.user_id > %d 
                AND c.id IS NULL",
                0
            )
        );

        // Count guest wishlists.
        $guests = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$lists_table} WHERE user_id IS NULL OR user_id = %d", 0 ) );

        // Count items only from wishlists that haven't been imported yet.
        $items = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) 
                FROM {$items_table} i 
                INNER JOIN {$lists_table} l ON i.wishlist_id = l.ID 
                LEFT JOIN {$collections_table} c ON l.wishlist_token = c.url_code
                WHERE l.user_id IS NOT NULL 
                AND l.user_id > %d 
                AND c.id IS NULL",
                0
            )
        );

        // Count unique users with wishlists that haven't been imported yet.
        $users = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(DISTINCT l.user_id) 
                FROM {$lists_table} l
                LEFT JOIN {$collections_table} c ON l.wishlist_token = c.url_code
                WHERE l.user_id IS NOT NULL 
                AND l.user_id > %d 
                AND c.id IS NULL",
                0
            )
        );

        // Count all YITH wishlists (both user and guest) that don't have a connection to SaveTo Wishlist table.
        $count_from_source = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$lists_table} l"
            )
        );

        // phpcs:enable

        return array(
            'countFromSource' => $count_from_source,
            'wishlists'       => $wishlists,
            'items'           => $items,
            'users'           => $users,
            'guests'          => $guests,
        );
    }

    /**
     * Get preview data.
     *
     * @since 1.0.0
     * @access public
     * @return array
     */
    public function get_preview_data(): array {
        global $wpdb;

        if ( ! $this->is_source_active() ) {
            return array();
        }

        // phpcs:disable

        $lists_table       = $wpdb->prefix . self::YITH_LISTS_TABLE;
        $items_table       = $wpdb->prefix . self::YITH_ITEMS_TABLE;
        $collections_table = Helper_Database::get_collections_table_name();

        // Get first 5 wishlists that haven't been imported yet.
        $wishlists = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT l.*, 
                    (SELECT COUNT(*) FROM {$items_table} WHERE wishlist_id = l.ID) as item_count
                FROM {$lists_table} l
                LEFT JOIN {$collections_table} c ON l.wishlist_token = c.url_code
                WHERE l.user_id IS NOT NULL 
                AND l.user_id > %d
                AND c.id IS NULL
                ORDER BY l.dateadded DESC
                LIMIT %d",
                0,
                5
            )
        );

        // phpcs:enable

        $preview = array();
        foreach ( $wishlists as $wishlist ) {
            $user      = get_user_by( 'id', $wishlist->user_id );
            $preview[] = array(
                'id'         => $wishlist->ID,
                'name'       => ! empty( $wishlist->wishlist_name ) ? $wishlist->wishlist_name : $this->get_default_wishlist_name(),
                'user_id'    => $wishlist->user_id,
                'user_email' => $user ? $user->user_email : '',
                'item_count' => (int) $wishlist->item_count,
                'created_at' => $wishlist->dateadded,
                'privacy'    => $wishlist->wishlist_privacy,
            );
        }

        return $preview;
    }

    /**
     * Get import batch.
     *
     * Cursor-based pagination: `$offset` is the highest source ID already seen
     * (0 on first call). The LEFT JOIN already excludes imported wishlists, so
     * an OFFSET would skip past unimported rows once the filter shrinks.
     *
     * @param int $offset Highest source wishlist ID already processed (cursor).
     * @param int $limit Limit.
     * @since 1.0.0
     * @since 1.0.6 Switched to cursor-based pagination — `$offset` is now the highest source ID already seen.
     * @access public
     * @return array
     */
    public function get_import_batch( int $offset, int $limit ): array {
        global $wpdb;

        if ( ! $this->is_source_active() ) {
            return array();
        }

        // phpcs:disable

        $lists_table       = $wpdb->prefix . self::YITH_LISTS_TABLE;
        $collections_table = Helper_Database::get_collections_table_name();

        // Get wishlists that haven't been imported yet.
        $wishlists = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->prepare(
                "SELECT l.*
                FROM {$lists_table} l
                LEFT JOIN {$collections_table} c ON l.wishlist_token = c.url_code
                WHERE l.user_id IS NOT NULL
                AND l.user_id > %d
                AND c.id IS NULL
                AND l.ID > %d
                ORDER BY l.ID ASC
                LIMIT %d",
                0,
                $offset,
                $limit
            )
        );

        // phpcs:enable

        return $wishlists;
    }

    /**
     * Import data.
     *
     * @param array $options Import options.
     * @since 1.0.0
     * @since 1.0.6 Drains all wishlists via cursor-based pagination loop.
     * @access public
     * @return array
     */
    public function import( array $options ): array {
        $this->clear_import_log();

        if ( ! $this->is_source_active() ) {
            return array(
                'success' => false,
                'message' => __( 'YITH WooCommerce Wishlist is not installed or active.', 'saveto-wishlist-lite-for-woocommerce' ),
            );
        }

        // Check if this is a batch request.
        $is_batch_request = isset( $options['batch_number'] ) && isset( $options['job_id'] );

        if ( $is_batch_request ) {
            return $this->import_batch( $options );
        }

        // Full import - set time limit for servers that allow it.
        $this->set_time_limit_with_fallback();

        $stats = array(
            'wishlists_imported' => 0,
            'items_imported'     => 0,
            'wishlists_skipped'  => 0,
            'items_skipped'      => 0,
            'users_affected'     => 0,
            'errors'             => array(),
        );

        $processed_users = array();
        $last_id         = 0;

        while ( true ) {
            $wishlists = $this->get_import_batch( $last_id, self::BATCH_SIZE );
            if ( empty( $wishlists ) ) {
                break;
            }

            $batch_result    = $this->process_wishlists_batch( $wishlists, $options, $processed_users, $stats );
            $stats           = $batch_result['stats'];
            $processed_users = $batch_result['processed_users'];

            $last_id = (int) end( $wishlists )->ID;
        }

        // Clear targeted caches for affected users.
        $this->clear_import_caches( $processed_users );

        return array(
            'success' => true,
            'stats'   => $stats,
            'log'     => $this->get_import_log(),
        );
    }

    /**
     * Import a single batch (for AJAX processing).
     *
     * @param array $options Import options including batch_number and job_id.
     * @since 1.0.3
     * @since 1.0.6 Drains all available wishlists per call via cursor-based pagination loop.
     * @access public
     * @return array
     */
    public function import_batch( array $options ): array {
        $job_id       = $options['job_id'] ?? '';
        $batch_number = (int) ( $options['batch_number'] ?? 0 );

        if ( empty( $job_id ) ) {
            return array(
                'success' => false,
                'message' => __( 'Invalid job ID.', 'saveto-wishlist-lite-for-woocommerce' ),
            );
        }

        // Get or initialize progress data.
        $progress = get_transient( STWLITE_IMPORT_PROGRESS_GROUP . $job_id );

        if ( false === $progress && 0 === $batch_number ) {
            // Initialize progress for first batch.
            $counts          = $this->get_source_data_count();
            $total_wishlists = $counts['wishlists'];
            $total_batches   = ceil( $total_wishlists / self::BATCH_SIZE );

            $progress = array(
                'user_id'            => get_current_user_id(),
                'job_id'             => $job_id,
                'status'             => 'processing',
                'current_batch'      => 0,
                'total_batches'      => $total_batches,
                'total_wishlists'    => $total_wishlists,
                'wishlists_imported' => 0,
                'items_imported'     => 0,
                'wishlists_skipped'  => 0,
                'items_skipped'      => 0,
                'users_affected'     => 0,
                'processed_users'    => array(),
                'errors'             => array(),
                'last_id'            => 0,
                'started_at'         => current_time( 'mysql' ),
            );
        } elseif ( false === $progress ) {
            return array(
                'success' => false,
                'message' => __( 'Import job not found or expired.', 'saveto-wishlist-lite-for-woocommerce' ),
            );
        }

        // Process current batch.
        $last_id   = (int) ( $progress['last_id'] ?? 0 );
        $wishlists = $this->get_import_batch( $last_id, self::BATCH_SIZE );

        if ( empty( $wishlists ) ) {
            // No more wishlists to process - mark as complete.
            $progress['status']       = 'completed';
            $progress['completed_at'] = current_time( 'mysql' );

            // Clear targeted caches for affected users.
            $this->clear_import_caches( $progress['processed_users'] );

            set_transient( STWLITE_IMPORT_PROGRESS_GROUP . $job_id, $progress, HOUR_IN_SECONDS );

            return array(
                'success'  => true,
                'complete' => true,
                'progress' => $progress,
            );
        }

        $stats           = array(
            'wishlists_imported' => $progress['wishlists_imported'],
            'items_imported'     => $progress['items_imported'],
            'wishlists_skipped'  => $progress['wishlists_skipped'],
            'items_skipped'      => $progress['items_skipped'],
            'users_affected'     => $progress['users_affected'],
            'errors'             => $progress['errors'],
        );
        $processed_users = $progress['processed_users'];

        $batch_result = $this->process_wishlists_batch( $wishlists, $options, $processed_users, $stats );

        // Update progress.
        $progress['current_batch']      = $batch_number;
        $progress['wishlists_imported'] = $batch_result['stats']['wishlists_imported'];
        $progress['items_imported']     = $batch_result['stats']['items_imported'];
        $progress['wishlists_skipped']  = $batch_result['stats']['wishlists_skipped'];
        $progress['items_skipped']      = $batch_result['stats']['items_skipped'];
        $progress['users_affected']     = $batch_result['stats']['users_affected'];
        $progress['processed_users']    = $batch_result['processed_users'];
        $progress['errors']             = $batch_result['stats']['errors'];
        $progress['last_id']            = (int) end( $wishlists )->ID;
        $progress['percentage']         = ( $progress['total_batches'] > 0 ) ? min( 100, round( ( ( $batch_number + 1 ) / $progress['total_batches'] ) * 100, 2 ) ) : 100;

        // Save progress (expires in 1 hour).
        set_transient( STWLITE_IMPORT_PROGRESS_GROUP . $job_id, $progress, HOUR_IN_SECONDS );

        return array(
            'success'  => true,
            'complete' => false,
            'progress' => $progress,
        );
    }

    /**
     * Process a batch of wishlists.
     *
     * @param array $wishlists Wishlists to process.
     * @param array $options Import options.
     * @param array $processed_users Already processed user IDs.
     * @param array $stats Current stats.
     * @since 1.0.3
     * @access protected
     * @return array Updated stats and processed_users.
     */
    protected function process_wishlists_batch( array $wishlists, array $options, array $processed_users, array $stats ): array {
        global $wpdb;

        $preserve_default = $options['preserve_default'] ?? true;
        $preserve_privacy = $options['preserve_privacy'] ?? true;

        foreach ( $wishlists as $yith_wishlist ) {
            // Validate user.
            if ( ! $this->validate_user( $yith_wishlist->user_id ) ) {
                ++$stats['wishlists_skipped'];
                $this->log( sprintf( 'Skipped wishlist ID %d - user does not exist', $yith_wishlist->ID ), 'warning' );
                continue;
            }

            $wishlist_name = $yith_wishlist->wishlist_name;
            if ( empty( $wishlist_name ) ) {
                $wishlist_name = $this->get_default_wishlist_name();
            }

            // phpcs:disable

            // Import items.
            $items_table = $wpdb->prefix . self::YITH_ITEMS_TABLE;
            $items       = $wpdb->get_results(
                $wpdb->prepare(
                    "SELECT * FROM {$items_table} WHERE wishlist_id = %d ORDER BY position ASC",
                    $yith_wishlist->ID
                )
            );

            // phpcs:enable

            $item_mapping = array_map(
                function ( $item ) {
                    $product_data = $this->resolve_product_variation( $item->prod_id );
                    return array(
                        'product_id'   => $product_data['product_id'],
                        'variation_id' => $product_data['variation_id'],
                        'quantity'     => $item->quantity,
                        'sort_order'   => $item->position,
                    );
                },
                $items
            );

            // Check if collection already exists using the url_code.
            $collection = Collections::instance()->get_collection( $yith_wishlist->wishlist_token, 'url_code' );
            if ( ! empty( $collection ) ) {
                ++$stats['wishlists_skipped'];
                $this->log( sprintf( 'Collection already exists for wishlist ID %d', $yith_wishlist->ID ), 'warning' );
                continue;
            }

            $collection_data = array(
                'user_id'     => $yith_wishlist->user_id,
                'name'        => $wishlist_name,
                'url_code'    => ! empty( $yith_wishlist->wishlist_token ) ? $yith_wishlist->wishlist_token : $this->generate_unique_url_code(),
                'status'      => 'publish',
                'is_default'  => $preserve_default ? (bool) $yith_wishlist->is_default : false,
                'is_public'   => $preserve_privacy ? $this->map_privacy( $yith_wishlist->wishlist_privacy ) : 0,
                'description' => '',
                'items'       => $item_mapping,
            );

            $collections       = Collections::instance();
            $collection_result = $collections->save_collection( $collection_data );

            // save_collection returns a Collection object.
            if ( empty( $collection_result ) || is_wp_error( $collection_result ) ) {
                ++$stats['wishlists_skipped'];
                $error_msg         = is_wp_error( $collection_result ) ? $collection_result->get_error_message() : 'Unknown error';
                $stats['errors'][] = sprintf( 'Failed to create collection for wishlist ID %d: %s', $yith_wishlist->ID, $error_msg );
                $this->log( sprintf( 'Failed to create collection for wishlist ID %d', $yith_wishlist->ID ), 'error' );
                continue;
            }

            // Get the ID from the Collection object.
            $target_collection_id = isset( $collection_result->id ) ? (int) $collection_result->id : 0;

            if ( ! $target_collection_id ) {
                ++$stats['wishlists_skipped'];
                $this->log( sprintf( 'Failed to retrieve collection ID from result for wishlist ID %d', $yith_wishlist->ID ), 'error' );
                continue;
            }

            // Track user.
            if ( ! in_array( $yith_wishlist->user_id, $processed_users, true ) ) {
                $processed_users[] = $yith_wishlist->user_id;
                ++$stats['users_affected'];
            }

            // Count items imported.
            $stats['items_imported'] += count( $item_mapping );

            ++$stats['wishlists_imported'];
        }

        $stats['errors'] = $this->trim_errors( $stats['errors'] );

        return array(
            'stats'           => $stats,
            'processed_users' => $processed_users,
        );
    }

    /**
     * Map privacy setting from YITH to SaveTo.
     *
     * YITH: 0=public, 1=shared, 2=private
     * SaveTo: 1=public, 0=private
     *
     * @param int $yith_privacy YITH privacy value.
     * @since 1.0.0
     * @access protected
     * @return int
     */
    protected function map_privacy( int $yith_privacy ): int {
        return ( 2 === $yith_privacy ) ? 0 : 1;
    }

    /**
     * Get YITH's configured default wishlist title with a safe fallback.
     *
     * @since 1.0.6
     * @access protected
     * @return string
     */
    protected function get_default_wishlist_name(): string {
        $default = get_option( 'yith_wcwl_wishlist_title', '' );

        if ( ! empty( $default ) ) {
            return (string) $default;
        }

        return $this->default_wishlist_fallback();
    }
}
