<?php
namespace Codexonics\PrimeMoverFramework\compatibility;

/*
 * This file is part of the Codexonics.PrimeMoverFramework package.
 *
 * (c) Codexonics Ltd
 *
 * This package is Open Source Software. For the full copyright and license
 * information, please view the LICENSE file which was distributed with this
 * source code.
 */

use Codexonics\PrimeMoverFramework\classes\PrimeMover;
use SplFixedArray;
use WP_Error;
use wpdb;

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

/**
 * Prime Mover WooCommerce Compatibility Class
 * Helper class for interacting with WooCommerce plugin
 *
 */
class PrimeMoverWooCommerceCompat
{     
    private $prime_mover;
    private $woocommerce_plugin;
    private $wc_order_posttype;
    
    /**
     * Construct
     * @param PrimeMover $prime_mover
     * @param array $utilities
     */
    public function __construct(PrimeMover $prime_mover, $utilities = [])
    {
        $this->prime_mover = $prime_mover;
        $this->woocommerce_plugin = 'woocommerce/woocommerce.php';
        $this->wc_order_posttype = 'shop_order';
    }
        
    /**
     * Get order post type
     * @return string
     */
    public function getOrderPostType()
    {
        return $this->wc_order_posttype;
    }
    
    /**
     * Get WooCommerce plugin
     * @return string
     */
    public function getWooCommercePlugin()
    {
        return $this->woocommerce_plugin;
    }
    
    /**
     * Get Prime Mover instance
     * @return \Codexonics\PrimeMoverFramework\classes\PrimeMover
     */
    public function getPrimeMover()
    {
        return $this->prime_mover;
    }
    
    /**
     * Get hooked methods
     * @return \Codexonics\PrimeMoverFramework\classes\PrimeMoverHookedMethods
     */
    public function getPrimeMoverHookedMethods()
    {
        return $this->getPrimeMover()->getHookedMethods();
    }
    
    /**
     * Get progess handlers
     * @return \Codexonics\PrimeMoverFramework\classes\PrimeMoverProgressHandlers
     */
    public function getProgressHandlers()
    {
        return $this->getPrimeMoverHookedMethods()->getProgressHandlers();
    }
    
    /**
     * Get system functions
     * @return \Codexonics\PrimeMoverFramework\classes\PrimeMoverSystemFunctions
     */
    public function getSystemFunctions()
    {
        return $this->getPrimeMover()->getSystemFunctions();
    }
    
    /**
     * Get system authorization
     * @return \Codexonics\PrimeMoverFramework\classes\PrimeMoverSystemAuthorization
     */
    public function getSystemAuthorization()
    {
        return $this->getPrimeMover()->getSystemAuthorization();
    }
 
    /**
     * Get user queries
     * @return \Codexonics\PrimeMoverFramework\users\PrimeMoverUserQueries
     */
    public function getUserQueries()
    {
        return $this->getPrimeMover()->getImporter()->getUsersObject()->getUserUtilities()->getUserFunctions()->getUserQueries();
    }
    
    /**
     * Initialize hooks
     */
    public function initHooks()
    {
        add_action('prime_mover_before_post_author_update', [$this, 'maybeReconnectWcOrdersWithMigratedUsers'], 10, 6);
        add_filter('prime_mover_do_process_thirdparty_data', [$this, 'maybeAdjustUserIdsCustomerLookup'], 10, 3);
        
        add_filter('prime_mover_do_process_thirdparty_data', [$this, 'maybeAdjustUserIdsDownloadPermissions'], 11, 3);
        add_filter('prime_mover_do_process_thirdparty_data', [$this, 'maybeAdjustUserIdsDownloadLog'], 12, 3);
    } 
 
    /**
     * Adjust customer Ids in WC download log table
     * Hooked to `prime_mover_do_process_thirdparty_data` filter - priority 12
     * @param array $ret
     * @param number $blogid_to_import
     * @param number $start_time
     * @return array
     */
    public function maybeAdjustUserIdsDownloadLog($ret = [], $blogid_to_import = 0, $start_time = 0)
    {
        $validation_error = $this->maybeRequisitesNotMeetForAdjustment($ret, $blogid_to_import);
        if (is_array($validation_error)) {
            return $validation_error;
        }
        
        if (!empty($ret['3rdparty_current_function']) && __FUNCTION__ !== $ret['3rdparty_current_function']) {
            return $ret;
        }
        
        $ret['3rdparty_current_function'] = __FUNCTION__;
        $table = 'wc_download_log';
        $leftoff_identifier = '3rdparty_dl_log_leftoff';
        
        $primary_index = 'download_log_id';
        $column_strings = 'download_log_id, user_id';
        $update_variable = '3rdparty_dl_log_updated';
        
        $progress_identifier = 'download log table';
        $last_processor = true;
        
        return $this->dBCustomerUserIdsHelper($ret, $table, $blogid_to_import, $leftoff_identifier, $primary_index, $column_strings,
            $update_variable, $progress_identifier, $start_time, $last_processor);
    }
    
    /**
     * Adjust customer Ids in WC download permissions table
     * Hooked to `prime_mover_do_process_thirdparty_data` filter - priority 11
     * @param array $ret
     * @param number $blogid_to_import
     * @param number $start_time
     * @return array
     */
    public function maybeAdjustUserIdsDownloadPermissions($ret = [], $blogid_to_import = 0, $start_time = 0)
    {
        $validation_error = $this->maybeRequisitesNotMeetForAdjustment($ret, $blogid_to_import);
        if (is_array($validation_error)) {
            return $validation_error;
        }
        
        if (!empty($ret['3rdparty_current_function']) && __FUNCTION__ !== $ret['3rdparty_current_function']) {
            return $ret;
        }
        
        $ret['3rdparty_current_function'] = __FUNCTION__;
        $table = 'woocommerce_downloadable_product_permissions';
        $leftoff_identifier = '3rdparty_cust_dl_leftoff';
        
        $primary_index = 'permission_id';
        $column_strings = 'permission_id, user_id';
        $update_variable = '3rdparty_cust_dl_updated';
        
        $progress_identifier = 'download permissions table';
        $last_processor = false;
        
        return $this->dBCustomerUserIdsHelper($ret, $table, $blogid_to_import, $leftoff_identifier, $primary_index, $column_strings,
            $update_variable, $progress_identifier, $start_time, $last_processor);
    }
    
    /**
     * Adjust customer Ids in WC customer lookup table
     * Hooked to `prime_mover_do_process_thirdparty_data` filter - priority 10
     * @param array $ret
     * @param number $blogid_to_import
     * @param number $start_time
     * @return array
     */
    public function maybeAdjustUserIdsCustomerLookup($ret = [], $blogid_to_import = 0, $start_time = 0)
    {        
        $validation_error = $this->maybeRequisitesNotMeetForAdjustment($ret, $blogid_to_import);
        if (is_array($validation_error)) {
            return $validation_error;
        }
        
        if (!empty($ret['3rdparty_current_function']) && __FUNCTION__ !== $ret['3rdparty_current_function']) {
            return $ret;
        }
        
        $ret['3rdparty_current_function'] = __FUNCTION__; 
        $table = 'wc_customer_lookup';
        $leftoff_identifier = '3rdparty_customers_leftoff';
        
        $primary_index = 'customer_id';
        $column_strings = 'customer_id, user_id';
        $update_variable = '3rdparty_customers_updated';
        
        $progress_identifier = 'customers lookup table';
        $last_processor = false;       
        $handle_unique_constraint = 'user_id';
        
        return $this->dBCustomerUserIdsHelper($ret, $table, $blogid_to_import, $leftoff_identifier, $primary_index, $column_strings,
            $update_variable, $progress_identifier, $start_time, $last_processor, $handle_unique_constraint);
    }
     
    /**
     * Drop indexes constraint
     * Must be done on switched tables
     * @param string $table
     * @param wpdb $wpdb
     * @param string $index
     */
    protected function dropIndexesConstraint($table = '', wpdb $wpdb, $index = '')
    {
        $wpdb->query("ALTER TABLE {$wpdb->prefix}{$table} DROP INDEX {$index}");
    }
    
    /**
     * Add indexes constraint
     * Must be done on switched tables
     * @param string $table
     * @param wpdb $wpdb
     * @param string $index
     */
    protected function enForceIndexesConstraint($table = '', wpdb $wpdb, $index = '')
    {
        $wpdb->query("ALTER TABLE {$wpdb->prefix}{$table} ADD CONSTRAINT {$index} UNIQUE ({$index})");
    }
    
    /**
     * Do flexible validation, if requisites not meet this returns $ret array
     * Otherwise return false.
     * @param array $ret
     * @param number $blogid_to_import
     * @return string|boolean
     */
    protected function maybeRequisitesNotMeetForAdjustment($ret = [], $blogid_to_import = 0)
    {
        if (!$this->getSystemAuthorization()->isUserAuthorized()) {
            $ret['error'] = esc_html__('Unauthorized', 'prime-mover');
            return $ret;
        }
        
        if (!isset($ret['imported_package_footprint']['plugins'][$this->getWooCommercePlugin()])) {
            return $ret;
        }
        
        if (!isset($ret['user_equivalence']) || !$blogid_to_import) {
            return $ret;
        }
        
        $mismatch_count = 0;
        if (isset($ret['user_mismatch_count'])) {
            $mismatch_count = $ret['user_mismatch_count'];
        }
        
        if (!$mismatch_count ) {
            do_action('prime_mover_log_processed_events', "Post mismatch count is zero, skipping WC customer lookup update.", $blogid_to_import, 'import', __FUNCTION__, $this);
            return $ret;
        }
        
        return false;
    }
    
    /**
     * dB Update Helper
     * @param array $ret
     * @param string $table 
     * @param number $blogid_to_import
     * @param string $leftoff_identifier 
     * @param string $primary_index 
     * @param string $column_strings 
     * @param string $update_variable 
     * @param string $progress_identifier
     * @param number $start_time
     * @param boolean $last_processor
     * @param string $handle_unique_constraint
     * @return array
     */
    protected function dBCustomerUserIdsHelper($ret = [], $table = '', $blogid_to_import = 0, $leftoff_identifier = '', $primary_index = '', $column_strings = '', 
        $update_variable = '', $progress_identifier = '', $start_time = 0, $last_processor = false, $handle_unique_constraint = '')
    {
        $this->getSystemFunctions()->switchToBlog($blogid_to_import);
        
        global $wpdb;
        $wpdb->flush();
        
        if ($handle_unique_constraint) {
            $this->dropIndexesConstraint($table, $wpdb, $handle_unique_constraint);
        }
        
        $table_exists = false;
        if ($wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}{$table}';")) {
            $table_exists = true;
        }
        
        if (!$table_exists) {
            $this->getSystemFunctions()->restoreCurrentBlog();
            return $ret;
        }
        
        $query = $this->seekCustomersToUpdateQuery($ret, $leftoff_identifier, $table, $primary_index, $column_strings);
        $customers_updated = 0;
        if (isset($ret[$update_variable])) {
            $customers_updated = $ret[$update_variable];
        }
        
        $update_customers_progress = '';
        if ($customers_updated) {
            $update_customers_progress = sprintf(esc_html__('%d completed', 'prime-mover'), $customers_updated);
        }
        $this->getProgressHandlers()->updateTrackerProgress(sprintf(esc_html__('Updating %s.. %s', 'prime-mover'), $progress_identifier, $update_customers_progress), 'import' );
        $user_equivalence = $ret['user_equivalence'];
        while ($results = $wpdb->get_results($query, ARRAY_A)) {
            if (empty($results)) {
                break;
            } else {
                $ret = $this->updateCustomerIds($results, $user_equivalence, $ret, $update_variable, $column_strings, $table, $leftoff_identifier);
            }
            
            $query = $this->seekCustomersToUpdateQuery($ret, $leftoff_identifier, $table, $primary_index, $column_strings);
            $retry_timeout = apply_filters('prime_mover_retry_timeout_seconds', PRIME_MOVER_RETRY_TIMEOUT_SECONDS, __FUNCTION__);
            if ( (microtime(true) - $start_time) > $retry_timeout) {
                $this->getSystemFunctions()->restoreCurrentBlog();
                do_action('prime_mover_log_processed_events', "$retry_timeout seconds time out on updating {$progress_identifier}" , $blogid_to_import, 'import', __FUNCTION__, $this);
                
                $ret['prime_mover_thirdparty_processing_retry'] = true;
                return $ret;
            }
        }
        
        $ret = $this->cleanUpRetArrayAfterCustomerIdProcessing($ret, $leftoff_identifier, $update_variable, $last_processor, $handle_unique_constraint, $table, $wpdb);
        $this->getSystemFunctions()->restoreCurrentBlog();
        
        return $ret;  
    }
    
    /**
     * Cleanup $ret array after processing.
     * @param array $ret
     * @param string $leftoff_identifier
     * @param string $update_variable
     * @param boolean $last_processor
     * @param string $handle_unique_constraint
     * @param string $table
     * @param wpdb $wpdb
     * @return array
     */
    protected function cleanUpRetArrayAfterCustomerIdProcessing($ret = [], $leftoff_identifier = '', $update_variable = '', 
        $last_processor = false, $handle_unique_constraint = '', $table = '', wpdb $wpdb)
    {
        if (isset($ret[$leftoff_identifier])) {
            unset($ret[$leftoff_identifier]);
        }
        
        if (isset($ret[$update_variable])) {
            unset($ret[$update_variable]);
        }
        
        if (isset($ret['3rdparty_current_function'])) {
            unset($ret['3rdparty_current_function']);
        }
        
        if (isset($ret['prime_mover_thirdparty_processing_retry']) && $last_processor) {
            unset($ret['prime_mover_thirdparty_processing_retry']);
        }       
         
        if ($last_processor) {
            $this->invalidateReportCacheAfterMigration();
        }
        
        if ($handle_unique_constraint) {
            $this->enForceIndexesConstraint($table, $wpdb, $handle_unique_constraint);
        }
        
        return $ret;
    }
    
    /**
     * Invalidate WC Analytics report cache after migration
     */
    protected function invalidateReportCacheAfterMigration()
    {
        $transient_name  = 'woocommerce_reports-transient-version';        
        $transient_value = (string) time();
            
        set_transient($transient_name, $transient_value);        
    }
    
    /**
     * Update customer IDs to migrated user IDs
     * @param array $results
     * @param SplFixedArray $user_equivalence
     * @param array $ret
     * @param string $update_variable
     * @param string $column_strings
     * @param string $table
     * @param string $leftoff_identifier
     * @return array
     */
    protected function updateCustomerIds($results = [], SplFixedArray $user_equivalence, $ret = [], $update_variable = '', $column_strings = '', 
        $table = '', $leftoff_identifier = '')
    {
        if (empty($results)) {
            return;
        }
        $customers_updated = 0;
        if (isset($ret[$update_variable])) {
            $customers_updated = (int)$ret[$update_variable];
        }   
   
        $column_names = array_map('trim', explode("," , $column_strings));
        list($primary_index, $user_id_column) = $column_names;
        
        $primary_index_id = 0;
        foreach ($results as $result) {
            if (!isset($result[$primary_index]) || !isset($result[$user_id_column])) {
                continue;
            }
            
            $primary_index_id = (int)$result[$primary_index];            
            $user_id = (int)$result[$user_id_column];           
            
            if (!$primary_index_id || !$user_id) {
                continue;
            }
            
            if (!isset($user_equivalence[$user_id])) {
                continue;
            }
            
            $migrated_user_id = (int)$user_equivalence[$user_id];
            if (!$migrated_user_id) {
                continue;
            }
            
            if ($migrated_user_id === $user_id) {
                continue;
            }        
            
            $this->getUserQueries()->maybeEnableUserImportExportTestMode(3, false);
            $primary_index_id = $this->updateCustomerUserIdBySQL($primary_index_id, $migrated_user_id, $table, $primary_index, $user_id_column);            
            if ( ! is_wp_error($primary_index_id) || !$primary_index_id) {
                $customers_updated++;                
            }
        }
        
        if ($primary_index_id) {
            $ret[$leftoff_identifier] = $primary_index_id;
        }        
        
        $ret[$update_variable] = $customers_updated;        
        return $ret;
    }
    
    /**
     * Update migrated customer user ID via SQL for best performance
     * @param number $primary_index_id
     * @param number $migrated_user_id
     * @param string $table
     * @param string $primary_index
     * @param string $user_id_column
     * @return \WP_Error|number
     */
    private function updateCustomerUserIdBySQL($primary_index_id = 0, $migrated_user_id = 0, $table = '', $primary_index = '', $user_id_column = '')
    {
        global $wpdb;        
        $prep = $wpdb->prepare("
            UPDATE {$wpdb->prefix}{$table}
            SET {$user_id_column} = %d
            WHERE {$primary_index} = %d",
            $migrated_user_id, $primary_index_id
        );
        
        $res = $wpdb->query($prep);
        if (false === $res) {
            return new WP_Error('update_author_error', esc_html__( 'Error updating author', 'prime-mover'));
        }
        
        return $primary_index_id;
    }
        
    /**
     * Query customers to update using MySQL seek method for faster performance
     * @param array $ret
     * @param string $leftoff_identifier
     * @param string $table
     * @param string $primary_index
     * @param string $column_strings
     * @return string
     */
    protected function seekCustomersToUpdateQuery($ret = [], $leftoff_identifier = '', $table = '', $primary_index = '', $column_strings = '')
    {        
        global $wpdb;
        $where = 'WHERE user_id IS NOT NULL';
        
        $left_off = 0;
        if (isset($ret[$leftoff_identifier])) {
            $left_off = $ret[$leftoff_identifier];
        }
        
        if ($left_off) {
            $where .= $wpdb->prepare(" AND {$primary_index} < %d", $left_off);
        }
        
        $orderby = $wpdb->prepare("ORDER BY {$primary_index} DESC LIMIT %d", PRIME_MOVER_CUSTOMER_LOOKUP_LIMIT);  
        
        return "SELECT {$column_strings} FROM {$wpdb->prefix}{$table} {$where} {$orderby}";
    }
    
    /**
     * Maybe reconnect WC Orders with migrated users
     * Hooked to `prime_mover_before_post_author_update` action.
     * @param number $post_id
     * @param number $post_author
     * @param number $new_author
     * @param array $ret
     * @param SplFixedArray $user_equivalence
     * @param string $post_type
     */
    public function maybeReconnectWcOrdersWithMigratedUsers($post_id = 0, $post_author = 0, $new_author = 0, $ret = [], SplFixedArray $user_equivalence, $post_type = '')
    {
        if (!$this->getSystemAuthorization()->isUserAuthorized()) {
            return;
        }
        
        if (!isset($ret['imported_package_footprint']['plugins'][$this->getWooCommercePlugin()])) {
            return;
        }
           
        if (!$post_type || $this->getOrderPostType() !== $post_type) {
            return;
        }
        
        $unadjusted_customerid = get_post_meta($post_id, '_customer_user', true);
        $unadjusted_customerid = (int)$unadjusted_customerid;
        if (!$unadjusted_customerid) {
            return;
        }
        
        if (!isset($user_equivalence[$unadjusted_customerid])) {
       
            return;
        }
        
        $migrated_customer_id = (int)$user_equivalence[$unadjusted_customerid];
        if (!$migrated_customer_id) {
            return;
        }
        
        if ($unadjusted_customerid === $migrated_customer_id) {
            return;
        }
                      
        update_post_meta($post_id, '_customer_user', $migrated_customer_id);        
    }
}