<?php

namespace DeskMoloni\Libraries;

/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 * 
 * Product Synchronization Service
 * Enhanced bidirectional sync service for products between Perfex CRM and Moloni ERP
 * 
 * @package DeskMoloni
 * @subpackage Libraries
 * @category ProductSync
 * @author Descomplicar® - PHP Fullstack Engineer
 * @version 1.0.0
 */

defined('BASEPATH') or exit('No direct script access allowed');

use DeskMoloni\Libraries\EntityMappingService;
use DeskMoloni\Libraries\ErrorHandler;
use DeskMoloni\Libraries\MoloniApiClient;

class ProductSyncService
{
    protected $CI;
    protected $api_client;
    protected $entity_mapping;
    protected $error_handler;
    protected $model;
    
    // Product matching configuration
    const MATCH_SCORE_EXACT = 100;
    const MATCH_SCORE_HIGH = 85;
    const MATCH_SCORE_MEDIUM = 65;
    const MATCH_SCORE_LOW = 45;
    
    // Product types mapping
    const TYPE_PRODUCT = 'P';
    const TYPE_SERVICE = 'S';
    const TYPE_SUBSCRIPTION = 'U';
    
    public function __construct()
    {
        $this->CI = &get_instance();
        $this->CI->load->model('desk_moloni_model');
        $this->CI->load->model('items_model');
        
        $this->model = $this->CI->desk_moloni_model;
        $this->api_client = new MoloniApiClient();
        $this->entity_mapping = new EntityMappingService();
        $this->error_handler = new ErrorHandler();
        
        log_activity('ProductSyncService initialized');
    }
    
    /**
     * Sync product from Perfex to Moloni
     * 
     * @param int $perfex_item_id
     * @param bool $force_update
     * @param array $additional_data
     * @return array
     */
    public function sync_perfex_to_moloni($perfex_item_id, $force_update = false, $additional_data = [])
    {
        $start_time = microtime(true);
        
        try {
            // Get Perfex item/product data
            $perfex_item = $this->get_perfex_item($perfex_item_id);
            if (!$perfex_item) {
                throw new \Exception("Perfex item ID {$perfex_item_id} not found");
            }
            
            // Check existing mapping
            $mapping = $this->entity_mapping->get_mapping_by_perfex_id(
                EntityMappingService::ENTITY_PRODUCT,
                $perfex_item_id
            );
            
            // Validate sync conditions
            if (!$this->should_sync_to_moloni($mapping, $force_update)) {
                return [
                    'success' => true,
                    'message' => 'Product already synced and up to date',
                    'mapping_id' => $mapping ? $mapping->id : null,
                    'moloni_product_id' => $mapping ? $mapping->moloni_id : null,
                    'skipped' => true
                ];
            }
            
            // Check for conflicts if mapping exists
            if ($mapping && !$force_update) {
                $conflict_check = $this->check_sync_conflicts($mapping);
                if ($conflict_check['has_conflict']) {
                    return $this->handle_sync_conflict($mapping, $conflict_check);
                }
            }
            
            // Transform Perfex data to Moloni format
            $moloni_data = $this->map_perfex_to_moloni_product($perfex_item, $additional_data);
            
            // Find or create product in Moloni
            $moloni_result = $this->create_or_update_moloni_product($moloni_data, $mapping);
            
            if (!$moloni_result['success']) {
                throw new \Exception("Moloni API error: " . $moloni_result['message']);
            }
            
            $moloni_product_id = $moloni_result['product_id'];
            $action = $moloni_result['action'];
            
            // Sync product taxes if configured
            if (get_option('desk_moloni_sync_product_taxes') == '1') {
                $this->sync_product_taxes($perfex_item, $moloni_product_id);
            }
            
            // Sync product variants if they exist
            if (get_option('desk_moloni_sync_product_variants') == '1') {
                $this->sync_product_variants($perfex_item, $moloni_product_id);
            }
            
            // Update or create mapping
            $mapping_id = $this->update_or_create_mapping(
                EntityMappingService::ENTITY_PRODUCT,
                $perfex_item_id,
                $moloni_product_id,
                EntityMappingService::DIRECTION_PERFEX_TO_MOLONI,
                $mapping
            );
            
            // Log sync activity
            $execution_time = microtime(true) - $start_time;
            $this->log_sync_activity([
                'entity_type' => 'product',
                'entity_id' => $perfex_item_id,
                'action' => $action,
                'direction' => 'perfex_to_moloni',
                'status' => 'success',
                'mapping_id' => $mapping_id,
                'request_data' => json_encode($moloni_data),
                'response_data' => json_encode($moloni_result),
                'processing_time' => $execution_time,
                'perfex_data_hash' => $this->calculate_data_hash($perfex_item),
                'moloni_data_hash' => $this->calculate_data_hash($moloni_result['data'] ?? [])
            ]);
            
            return [
                'success' => true,
                'message' => "Product {$action}d successfully in Moloni",
                'mapping_id' => $mapping_id,
                'moloni_product_id' => $moloni_product_id,
                'action' => $action,
                'execution_time' => $execution_time,
                'data_changes' => $this->detect_data_changes($perfex_item, $moloni_result['data'] ?? [])
            ];
            
        } catch (\Exception $e) {
            return $this->handle_sync_error($e, [
                'entity_type' => 'product',
                'entity_id' => $perfex_item_id,
                'direction' => 'perfex_to_moloni',
                'execution_time' => microtime(true) - $start_time,
                'mapping' => $mapping ?? null
            ]);
        }
    }
    
    /**
     * Sync product from Moloni to Perfex
     * 
     * @param int $moloni_product_id
     * @param bool $force_update
     * @param array $additional_data
     * @return array
     */
    public function sync_moloni_to_perfex($moloni_product_id, $force_update = false, $additional_data = [])
    {
        $start_time = microtime(true);
        
        try {
            // Get Moloni product data
            $moloni_response = $this->api_client->get_product($moloni_product_id);
            if (!$moloni_response['success']) {
                throw new \Exception("Moloni product ID {$moloni_product_id} not found: " . $moloni_response['message']);
            }
            
            $moloni_product = $moloni_response['data'];
            
            // Check existing mapping
            $mapping = $this->entity_mapping->get_mapping_by_moloni_id(
                EntityMappingService::ENTITY_PRODUCT,
                $moloni_product_id
            );
            
            // Validate sync conditions
            if (!$this->should_sync_to_perfex($mapping, $force_update)) {
                return [
                    'success' => true,
                    'message' => 'Product already synced and up to date',
                    'mapping_id' => $mapping ? $mapping->id : null,
                    'perfex_item_id' => $mapping ? $mapping->perfex_id : null,
                    'skipped' => true
                ];
            }
            
            // Check for conflicts if mapping exists
            if ($mapping && !$force_update) {
                $conflict_check = $this->check_sync_conflicts($mapping);
                if ($conflict_check['has_conflict']) {
                    return $this->handle_sync_conflict($mapping, $conflict_check);
                }
            }
            
            // Transform Moloni data to Perfex format
            $perfex_data = $this->map_moloni_to_perfex_product($moloni_product, $additional_data);
            
            // Find or create product in Perfex
            $perfex_result = $this->create_or_update_perfex_product($perfex_data, $mapping);
            
            if (!$perfex_result['success']) {
                throw new \Exception("Perfex CRM error: " . $perfex_result['message']);
            }
            
            $perfex_item_id = $perfex_result['item_id'];
            $action = $perfex_result['action'];
            
            // Update or create mapping
            $mapping_id = $this->update_or_create_mapping(
                EntityMappingService::ENTITY_PRODUCT,
                $perfex_item_id,
                $moloni_product_id,
                EntityMappingService::DIRECTION_MOLONI_TO_PERFEX,
                $mapping
            );
            
            // Log sync activity
            $execution_time = microtime(true) - $start_time;
            $this->log_sync_activity([
                'entity_type' => 'product',
                'entity_id' => $perfex_item_id,
                'action' => $action,
                'direction' => 'moloni_to_perfex',
                'status' => 'success',
                'mapping_id' => $mapping_id,
                'request_data' => json_encode($moloni_product),
                'response_data' => json_encode($perfex_result),
                'processing_time' => $execution_time,
                'moloni_data_hash' => $this->calculate_data_hash($moloni_product),
                'perfex_data_hash' => $this->calculate_data_hash($perfex_result['data'] ?? [])
            ]);
            
            return [
                'success' => true,
                'message' => "Product {$action}d successfully in Perfex",
                'mapping_id' => $mapping_id,
                'perfex_item_id' => $perfex_item_id,
                'action' => $action,
                'execution_time' => $execution_time,
                'data_changes' => $this->detect_data_changes($moloni_product, $perfex_result['data'] ?? [])
            ];
            
        } catch (\Exception $e) {
            return $this->handle_sync_error($e, [
                'entity_type' => 'product',
                'entity_id' => $moloni_product_id,
                'direction' => 'moloni_to_perfex',
                'execution_time' => microtime(true) - $start_time,
                'mapping' => $mapping ?? null
            ]);
        }
    }
    
    /**
     * Check for synchronization conflicts
     * 
     * @param object $mapping
     * @return array
     */
    public function check_sync_conflicts($mapping)
    {
        try {
            $conflicts = [];
            
            // Get current data from both systems
            $perfex_item = $this->get_perfex_item($mapping->perfex_id);
            $moloni_response = $this->api_client->get_product($mapping->moloni_id);
            
            if (!$perfex_item || !$moloni_response['success']) {
                return ['has_conflict' => false];
            }
            
            $moloni_product = $moloni_response['data'];
            
            // Check modification timestamps
            $perfex_modified = $this->get_perfex_modification_time($mapping->perfex_id);
            $moloni_modified = $this->get_moloni_modification_time($mapping->moloni_id);
            $last_sync = max(
                strtotime($mapping->last_sync_perfex ?: '1970-01-01'),
                strtotime($mapping->last_sync_moloni ?: '1970-01-01')
            );
            
            $perfex_changed_after_sync = $perfex_modified > $last_sync;
            $moloni_changed_after_sync = $moloni_modified > $last_sync;
            
            if ($perfex_changed_after_sync && $moloni_changed_after_sync) {
                // Both sides modified since last sync - check for field conflicts
                $field_conflicts = $this->detect_product_field_conflicts($perfex_item, $moloni_product);
                
                if (!empty($field_conflicts)) {
                    $conflicts = [
                        'type' => 'data_conflict',
                        'message' => 'Both systems have been modified since last sync',
                        'field_conflicts' => $field_conflicts,
                        'perfex_modified' => date('Y-m-d H:i:s', $perfex_modified),
                        'moloni_modified' => date('Y-m-d H:i:s', $moloni_modified),
                        'last_sync' => $mapping->last_sync_perfex ?: $mapping->last_sync_moloni
                    ];
                }
            }
            
            // Check for stock conflicts if stock sync is enabled
            if (get_option('desk_moloni_sync_stock') == '1') {
                $stock_conflict = $this->check_stock_conflicts($perfex_item, $moloni_product, $last_sync);
                if ($stock_conflict) {
                    $conflicts['stock_conflict'] = $stock_conflict;
                }
            }
            
            return [
                'has_conflict' => !empty($conflicts),
                'conflict_details' => $conflicts
            ];
            
        } catch (\Exception $e) {
            $this->error_handler->log_error('sync', 'PRODUCT_CONFLICT_CHECK_FAILED', $e->getMessage(), [
                'mapping_id' => $mapping->id
            ]);
            
            return ['has_conflict' => false];
        }
    }
    
    /**
     * Detect field-level conflicts between product data
     * 
     * @param array $perfex_data
     * @param array $moloni_data
     * @return array
     */
    protected function detect_product_field_conflicts($perfex_data, $moloni_data)
    {
        $conflicts = [];
        
        // Define critical fields to check for conflicts
        $critical_fields = [
            'name' => ['perfex' => 'description', 'moloni' => 'name'],
            'reference' => ['perfex' => 'long_description', 'moloni' => 'reference'],
            'price' => ['perfex' => 'rate', 'moloni' => 'price'],
            'tax_rate' => ['perfex' => 'tax', 'moloni' => 'tax_id'],
            'unit' => ['perfex' => 'unit', 'moloni' => 'measurement_unit_id']
        ];
        
        foreach ($critical_fields as $field => $mappings) {
            $perfex_value = $this->normalize_field_value($perfex_data[$mappings['perfex']] ?? '');
            $moloni_value = $this->normalize_field_value($moloni_data[$mappings['moloni']] ?? '');
            
            if ($perfex_value !== $moloni_value && 
                !empty($perfex_value) && 
                !empty($moloni_value)) {
                
                $conflicts[$field] = [
                    'perfex_value' => $perfex_value,
                    'moloni_value' => $moloni_value,
                    'similarity_score' => $this->calculate_field_similarity($perfex_value, $moloni_value)
                ];
            }
        }
        
        return $conflicts;
    }
    
    /**
     * Check for stock level conflicts
     * 
     * @param array $perfex_item
     * @param array $moloni_product
     * @param int $last_sync
     * @return array|null
     */
    protected function check_stock_conflicts($perfex_item, $moloni_product, $last_sync)
    {
        // For Perfex CRM, stock management is typically in invoice items or custom fields
        // This is a placeholder for stock conflict detection logic
        
        $perfex_stock = $this->get_perfex_stock_level($perfex_item);
        $moloni_stock = $moloni_product['stock'] ?? 0;
        
        // Check if both stock levels have been modified since last sync
        $perfex_stock_modified = $this->get_perfex_stock_modification_time($perfex_item['itemid']);
        $moloni_stock_modified = $this->get_moloni_stock_modification_time($moloni_product['product_id']);
        
        if ($perfex_stock_modified > $last_sync && $moloni_stock_modified > $last_sync) {
            $stock_difference = abs($perfex_stock - $moloni_stock);
            $threshold = (float)get_option('desk_moloni_stock_conflict_threshold', 5.0);
            
            if ($stock_difference > $threshold) {
                return [
                    'perfex_stock' => $perfex_stock,
                    'moloni_stock' => $moloni_stock,
                    'difference' => $stock_difference,
                    'threshold' => $threshold,
                    'perfex_modified' => date('Y-m-d H:i:s', $perfex_stock_modified),
                    'moloni_modified' => date('Y-m-d H:i:s', $moloni_stock_modified)
                ];
            }
        }
        
        return null;
    }
    
    /**
     * Find potential product matches in Moloni
     * 
     * @param array $perfex_item
     * @return array
     */
    public function find_moloni_product_matches($perfex_item)
    {
        $matches = [];
        
        // Search by reference/SKU (highest priority)
        if (!empty($perfex_item['long_description'])) {
            $reference_matches = $this->api_client->search_products(['reference' => $perfex_item['long_description']]);
            if ($reference_matches['success'] && !empty($reference_matches['data'])) {
                foreach ($reference_matches['data'] as $product) {
                    $matches[] = array_merge($product, [
                        'match_score' => self::MATCH_SCORE_EXACT,
                        'match_type' => 'reference',
                        'match_criteria' => ['reference' => $perfex_item['long_description']]
                    ]);
                }
            }
        }
        
        // Search by name (high priority)
        if (!empty($perfex_item['description']) && empty($matches)) {
            $name_matches = $this->api_client->search_products(['name' => $perfex_item['description']]);
            if ($name_matches['success'] && !empty($name_matches['data'])) {
                foreach ($name_matches['data'] as $product) {
                    $similarity = $this->calculate_name_similarity($perfex_item['description'], $product['name']);
                    if ($similarity >= 0.7) {
                        $score = $similarity >= 0.9 ? self::MATCH_SCORE_HIGH : self::MATCH_SCORE_MEDIUM;
                        $matches[] = array_merge($product, [
                            'match_score' => $score,
                            'match_type' => 'name',
                            'match_criteria' => ['name' => $perfex_item['description']],
                            'similarity' => $similarity
                        ]);
                    }
                }
            }
        }
        
        // Search by EAN/Barcode if available
        if (isset($perfex_item['barcode']) && !empty($perfex_item['barcode']) && count($matches) < 3) {
            $barcode_matches = $this->api_client->search_products(['ean' => $perfex_item['barcode']]);
            if ($barcode_matches['success'] && !empty($barcode_matches['data'])) {
                foreach ($barcode_matches['data'] as $product) {
                    $matches[] = array_merge($product, [
                        'match_score' => self::MATCH_SCORE_EXACT,
                        'match_type' => 'ean',
                        'match_criteria' => ['ean' => $perfex_item['barcode']]
                    ]);
                }
            }
        }
        
        // Remove duplicates and sort by match score
        $matches = $this->deduplicate_matches($matches);
        usort($matches, function($a, $b) {
            return $b['match_score'] - $a['match_score'];
        });
        
        return array_slice($matches, 0, 10);
    }
    
    /**
     * Find potential product matches in Perfex
     * 
     * @param array $moloni_product
     * @return array
     */
    public function find_perfex_product_matches($moloni_product)
    {
        $matches = [];
        
        // Search by reference/SKU
        if (!empty($moloni_product['reference'])) {
            $reference_matches = $this->CI->items_model->search_items_by_reference($moloni_product['reference']);
            foreach ($reference_matches as $item) {
                $matches[] = array_merge((array)$item, [
                    'match_score' => self::MATCH_SCORE_EXACT,
                    'match_type' => 'reference',
                    'match_criteria' => ['reference' => $moloni_product['reference']]
                ]);
            }
        }
        
        // Search by name
        if (!empty($moloni_product['name']) && empty($matches)) {
            $name_matches = $this->CI->items_model->search_items_by_name($moloni_product['name']);
            foreach ($name_matches as $item) {
                $similarity = $this->calculate_name_similarity($moloni_product['name'], $item->description);
                if ($similarity >= 0.7) {
                    $score = $similarity >= 0.9 ? self::MATCH_SCORE_HIGH : self::MATCH_SCORE_MEDIUM;
                    $matches[] = array_merge((array)$item, [
                        'match_score' => $score,
                        'match_type' => 'name',
                        'match_criteria' => ['name' => $moloni_product['name']],
                        'similarity' => $similarity
                    ]);
                }
            }
        }
        
        // Search by EAN/Barcode
        if (!empty($moloni_product['ean']) && count($matches) < 3) {
            $barcode_matches = $this->CI->items_model->search_items_by_barcode($moloni_product['ean']);
            foreach ($barcode_matches as $item) {
                $matches[] = array_merge((array)$item, [
                    'match_score' => self::MATCH_SCORE_EXACT,
                    'match_type' => 'ean',
                    'match_criteria' => ['ean' => $moloni_product['ean']]
                ]);
            }
        }
        
        // Remove duplicates and sort by match score
        $matches = $this->deduplicate_matches($matches);
        usort($matches, function($a, $b) {
            return $b['match_score'] - $a['match_score'];
        });
        
        return array_slice($matches, 0, 10);
    }
    
    /**
     * Map Perfex item to Moloni product format
     * 
     * @param array $perfex_item
     * @param array $additional_data
     * @return array
     */
    protected function map_perfex_to_moloni_product($perfex_item, $additional_data = [])
    {
        $mapped_data = [
            'category_id' => $this->get_default_product_category(),
            'type' => $this->convert_product_type($perfex_item['type'] ?? 'product'),
            'name' => $perfex_item['description'] ?? '',
            'reference' => $perfex_item['long_description'] ?? '',
            'price' => (float)($perfex_item['rate'] ?? 0),
            'price_with_taxes' => 0, // Will be calculated
            'unit_id' => $this->convert_measurement_unit($perfex_item['unit'] ?? ''),
            'has_stock' => (int)get_option('desk_moloni_track_stock', 1),
            'stock' => $this->get_perfex_stock_level($perfex_item),
            'minimum_stock' => 0,
            'pos_favorite' => 0,
            'at_product_category' => '',
            'exemption_reason' => '',
            'exemption_reason_code' => '',
            'warehouse_id' => $this->get_default_warehouse(),
            'tax_id' => $this->convert_tax_rate($perfex_item['tax'] ?? 0),
            'summary' => $this->generate_product_summary($perfex_item),
            'notes' => $perfex_item['notes'] ?? ''
        ];
        
        // Add EAN/Barcode if available
        if (isset($perfex_item['barcode']) && !empty($perfex_item['barcode'])) {
            $mapped_data['ean'] = $perfex_item['barcode'];
        }
        
        // Calculate price with taxes
        $tax_rate = $this->get_tax_rate_percentage($mapped_data['tax_id']);
        $mapped_data['price_with_taxes'] = $mapped_data['price'] * (1 + ($tax_rate / 100));
        
        // Apply additional data overrides
        $mapped_data = array_merge($mapped_data, $additional_data);
        
        // Clean and validate data
        return $this->clean_moloni_product_data($mapped_data);
    }
    
    /**
     * Map Moloni product to Perfex item format
     * 
     * @param array $moloni_product
     * @param array $additional_data
     * @return array
     */
    protected function map_moloni_to_perfex_product($moloni_product, $additional_data = [])
    {
        $mapped_data = [
            'description' => $moloni_product['name'] ?? '',
            'long_description' => $moloni_product['reference'] ?? '',
            'rate' => (float)($moloni_product['price'] ?? 0),
            'tax' => $this->convert_moloni_tax_to_perfex($moloni_product['tax_id'] ?? 0),
            'unit' => $this->convert_moloni_unit_to_perfex($moloni_product['unit_id'] ?? 0),
            'group_id' => $this->get_default_item_group()
        ];
        
        // Add barcode/EAN if available
        if (!empty($moloni_product['ean'])) {
            $mapped_data['barcode'] = $moloni_product['ean'];
        }
        
        // Apply additional data overrides
        $mapped_data = array_merge($mapped_data, $additional_data);
        
        // Clean and validate data
        return $this->clean_perfex_product_data($mapped_data);
    }
    
    /**
     * Create or update product in Moloni
     * 
     * @param array $moloni_data
     * @param object $mapping
     * @return array
     */
    protected function create_or_update_moloni_product($moloni_data, $mapping = null)
    {
        if ($mapping && $mapping->moloni_id) {
            // Update existing product
            $response = $this->api_client->update_product($mapping->moloni_id, $moloni_data);
            
            if ($response['success']) {
                return [
                    'success' => true,
                    'product_id' => $mapping->moloni_id,
                    'action' => 'update',
                    'data' => $response['data']
                ];
            }
        }
        
        // Create new product or fallback to create if update failed
        $response = $this->api_client->create_product($moloni_data);
        
        if ($response['success']) {
            return [
                'success' => true,
                'product_id' => $response['data']['product_id'],
                'action' => 'create',
                'data' => $response['data']
            ];
        }
        
        return [
            'success' => false,
            'message' => $response['message'] ?? 'Unknown error creating/updating product in Moloni'
        ];
    }
    
    /**
     * Create or update product in Perfex
     * 
     * @param array $perfex_data
     * @param object $mapping
     * @return array
     */
    protected function create_or_update_perfex_product($perfex_data, $mapping = null)
    {
        if ($mapping && $mapping->perfex_id) {
            // Update existing item
            $result = $this->CI->items_model->update($perfex_data, $mapping->perfex_id);
            
            if ($result) {
                return [
                    'success' => true,
                    'item_id' => $mapping->perfex_id,
                    'action' => 'update',
                    'data' => $perfex_data
                ];
            }
        }
        
        // Create new item or fallback to create if update failed
        $item_id = $this->CI->items_model->add($perfex_data);
        
        if ($item_id) {
            return [
                'success' => true,
                'item_id' => $item_id,
                'action' => 'create',
                'data' => $perfex_data
            ];
        }
        
        return [
            'success' => false,
            'message' => 'Failed to create/update item in Perfex CRM'
        ];
    }
    
    // Additional helper methods...
    
    /**
     * Get Perfex item data
     * 
     * @param int $item_id
     * @return array|null
     */
    protected function get_perfex_item($item_id)
    {
        $item = $this->CI->items_model->get($item_id);
        return $item ? (array)$item : null;
    }
    
    /**
     * Convert product type between systems
     * 
     * @param string $perfex_type
     * @return string
     */
    protected function convert_product_type($perfex_type)
    {
        $type_mappings = [
            'product' => self::TYPE_PRODUCT,
            'service' => self::TYPE_SERVICE,
            'subscription' => self::TYPE_SUBSCRIPTION
        ];
        
        return $type_mappings[$perfex_type] ?? self::TYPE_PRODUCT;
    }
    
    /**
     * Get Perfex stock level
     * 
     * @param array $perfex_item
     * @return float
     */
    protected function get_perfex_stock_level($perfex_item)
    {
        // Perfex CRM doesn't have built-in stock management
        // This could be from custom fields or a stock management addon
        return (float)($perfex_item['stock_quantity'] ?? 0);
    }
    
    /**
     * Clean and validate Moloni product data
     * 
     * @param array $data
     * @return array
     */
    protected function clean_moloni_product_data($data)
    {
        // Ensure required fields
        if (empty($data['name'])) {
            $data['name'] = 'Imported Product';
        }
        
        // Validate numeric fields
        $data['price'] = max(0, (float)$data['price']);
        $data['price_with_taxes'] = max(0, (float)$data['price_with_taxes']);
        $data['stock'] = max(0, (float)$data['stock']);
        
        // Sanitize strings
        $data['name'] = trim(substr($data['name'], 0, 255));
        $data['reference'] = trim(substr($data['reference'], 0, 100));
        
        return $data;
    }
    
    /**
     * Clean and validate Perfex product data
     * 
     * @param array $data
     * @return array
     */
    protected function clean_perfex_product_data($data)
    {
        // Ensure required fields
        if (empty($data['description'])) {
            $data['description'] = 'Imported Product';
        }
        
        // Validate numeric fields
        $data['rate'] = max(0, (float)$data['rate']);
        
        // Sanitize strings
        $data['description'] = trim(substr($data['description'], 0, 255));
        $data['long_description'] = trim(substr($data['long_description'], 0, 500));
        
        return $data;
    }
    
    /**
     * Calculate data hash for change detection
     * 
     * @param array $data
     * @return string
     */
    protected function calculate_data_hash($data)
    {
        ksort($data);
        return md5(serialize($data));
    }
    
    /**
     * Handle sync error
     * 
     * @param \Exception $e
     * @param array $context
     * @return array
     */
    protected function handle_sync_error($e, $context)
    {
        $execution_time = $context['execution_time'];
        
        // Update mapping with error if exists
        if (isset($context['mapping']) && $context['mapping']) {
            $this->entity_mapping->update_mapping_status(
                $context['mapping']->id,
                EntityMappingService::STATUS_ERROR,
                $e->getMessage()
            );
        }
        
        // Log error
        $this->error_handler->log_error('sync', 'PRODUCT_SYNC_FAILED', $e->getMessage(), $context);
        
        // Log sync activity
        $this->log_sync_activity([
            'entity_type' => $context['entity_type'],
            'entity_id' => $context['entity_id'],
            'action' => 'sync',
            'direction' => $context['direction'],
            'status' => 'error',
            'error_message' => $e->getMessage(),
            'processing_time' => $execution_time
        ]);
        
        return [
            'success' => false,
            'message' => $e->getMessage(),
            'execution_time' => $execution_time,
            'error_code' => $e->getCode()
        ];
    }
    
    /**
     * Log sync activity
     * 
     * @param array $data
     */
    protected function log_sync_activity($data)
    {
        $this->model->log_sync_activity($data);
    }
    
    /**
     * Check if should sync to Moloni
     * 
     * @param object $mapping
     * @param bool $force_update
     * @return bool
     */
    protected function should_sync_to_moloni($mapping, $force_update)
    {
        if ($force_update) {
            return true;
        }
        
        if (!$mapping) {
            return true; // No mapping exists, should sync
        }
        
        if ($mapping->sync_status === EntityMappingService::STATUS_ERROR) {
            return true; // Retry failed syncs
        }
        
        // Check if Perfex data changed since last sync
        $perfex_modified = $this->get_perfex_modification_time($mapping->perfex_id);
        $last_sync = strtotime($mapping->last_sync_perfex ?: '1970-01-01');
        
        return $perfex_modified > $last_sync;
    }
    
    /**
     * Check if should sync to Perfex
     * 
     * @param object $mapping
     * @param bool $force_update
     * @return bool
     */
    protected function should_sync_to_perfex($mapping, $force_update)
    {
        if ($force_update) {
            return true;
        }
        
        if (!$mapping) {
            return true; // No mapping exists, should sync
        }
        
        if ($mapping->sync_status === EntityMappingService::STATUS_ERROR) {
            return true; // Retry failed syncs
        }
        
        // Check if Moloni data changed since last sync
        $moloni_modified = $this->get_moloni_modification_time($mapping->moloni_id);
        $last_sync = strtotime($mapping->last_sync_moloni ?: '1970-01-01');
        
        return $moloni_modified > $last_sync;
    }
    
    /**
     * Update or create entity mapping
     * 
     * @param string $entity_type
     * @param int $perfex_id
     * @param int $moloni_id
     * @param string $direction
     * @param object $mapping
     * @return int
     */
    protected function update_or_create_mapping($entity_type, $perfex_id, $moloni_id, $direction, $mapping = null)
    {
        $mapping_data = [
            'sync_status' => EntityMappingService::STATUS_SYNCED,
            'sync_direction' => $direction,
            'updated_at' => date('Y-m-d H:i:s')
        ];
        
        // Update sync timestamps based on direction
        if ($direction === EntityMappingService::DIRECTION_PERFEX_TO_MOLONI) {
            $mapping_data['last_sync_perfex'] = date('Y-m-d H:i:s');
        } elseif ($direction === EntityMappingService::DIRECTION_MOLONI_TO_PERFEX) {
            $mapping_data['last_sync_moloni'] = date('Y-m-d H:i:s');
        } else {
            $mapping_data['last_sync_perfex'] = date('Y-m-d H:i:s');
            $mapping_data['last_sync_moloni'] = date('Y-m-d H:i:s');
        }
        
        if ($mapping) {
            // Update existing mapping
            $this->entity_mapping->update_mapping($mapping->id, $mapping_data);
            return $mapping->id;
        } else {
            // Create new mapping
            return $this->entity_mapping->create_mapping(
                $entity_type,
                $perfex_id,
                $moloni_id,
                $direction
            );
        }
    }
    
    /**
     * Get Perfex product modification time
     * 
     * @param int $item_id
     * @return int
     */
    protected function get_perfex_modification_time($item_id)
    {
        $item = $this->CI->db->select('date_created')
                            ->where('id', $item_id)
                            ->get(db_prefix() . 'items')
                            ->row();
        
        if (!$item) {
            return 0;
        }
        
        return strtotime($item->date_created);
    }
    
    /**
     * Get Moloni product modification time
     * 
     * @param int $moloni_id
     * @return int
     */
    protected function get_moloni_modification_time($moloni_id)
    {
        $response = $this->api_client->get_product($moloni_id);
        
        if (!$response['success']) {
            return 0;
        }
        
        $product = $response['data'];
        return isset($product['last_modified']) ? strtotime($product['last_modified']) : time();
    }
    
    /**
     * Handle sync conflict
     * 
     * @param object $mapping
     * @param array $conflict_check
     * @return array
     */
    protected function handle_sync_conflict($mapping, $conflict_check)
    {
        // Update mapping status to conflict
        $this->entity_mapping->update_mapping_status(
            $mapping->id,
            EntityMappingService::STATUS_CONFLICT,
            json_encode($conflict_check['conflict_details'])
        );
        
        // Get conflict resolution strategy
        $strategy = get_option('desk_moloni_conflict_strategy', 'manual');
        
        if ($strategy === 'manual') {
            return [
                'success' => false,
                'message' => 'Sync conflict detected - manual resolution required',
                'conflict_details' => $conflict_check['conflict_details'],
                'mapping_id' => $mapping->id,
                'requires_manual_resolution' => true
            ];
        }
        
        // Auto-resolve based on strategy
        return $this->auto_resolve_conflict($mapping, $conflict_check, $strategy);
    }
    
    /**
     * Auto-resolve conflict based on strategy
     * 
     * @param object $mapping
     * @param array $conflict_check
     * @param string $strategy
     * @return array
     */
    protected function auto_resolve_conflict($mapping, $conflict_check, $strategy)
    {
        switch ($strategy) {
            case 'newest':
                $perfex_modified = strtotime($conflict_check['conflict_details']['perfex_modified']);
                $moloni_modified = strtotime($conflict_check['conflict_details']['moloni_modified']);
                
                if ($perfex_modified > $moloni_modified) {
                    return $this->sync_perfex_to_moloni($mapping->perfex_id, true);
                } else {
                    return $this->sync_moloni_to_perfex($mapping->moloni_id, true);
                }
                
            case 'perfex_wins':
                return $this->sync_perfex_to_moloni($mapping->perfex_id, true);
                
            case 'moloni_wins':
                return $this->sync_moloni_to_perfex($mapping->moloni_id, true);
                
            default:
                return [
                    'success' => false,
                    'message' => 'Unknown conflict resolution strategy',
                    'conflict_details' => $conflict_check['conflict_details']
                ];
        }
    }
    
    /**
     * Detect data changes between versions
     * 
     * @param array $old_data
     * @param array $new_data
     * @return array
     */
    protected function detect_data_changes($old_data, $new_data)
    {
        $changes = [];
        
        foreach ($new_data as $key => $new_value) {
            $old_value = $old_data[$key] ?? null;
            
            if ($old_value !== $new_value) {
                $changes[$key] = [
                    'old' => $old_value,
                    'new' => $new_value
                ];
            }
        }
        
        return $changes;
    }
}