<?php

namespace DeskMoloni\Libraries;

/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 * 
 * Estimate Synchronization Service
 * Enhanced bidirectional sync service for estimates between Perfex CRM and Moloni ERP
 * 
 * @package DeskMoloni
 * @subpackage Libraries
 * @category EstimateSync
 * @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;
use DeskMoloni\Libraries\ClientSyncService;
use DeskMoloni\Libraries\ProductSyncService;

class EstimateSyncService
{
    protected $CI;
    protected $api_client;
    protected $entity_mapping;
    protected $error_handler;
    protected $model;
    protected $client_sync;
    protected $product_sync;
    
    // Estimate status mapping
    const STATUS_DRAFT = 1;
    const STATUS_SENT = 2;
    const STATUS_DECLINED = 3;
    const STATUS_ACCEPTED = 4;
    const STATUS_EXPIRED = 5;
    
    // Moloni document types for estimates
    const MOLONI_DOC_TYPE_QUOTE = 'quote';
    const MOLONI_DOC_TYPE_PROFORMA = 'proforma';
    const MOLONI_DOC_TYPE_BUDGET = 'budget';
    
    // Conflict resolution strategies
    const CONFLICT_STRATEGY_MANUAL = 'manual';
    const CONFLICT_STRATEGY_NEWEST = 'newest';
    const CONFLICT_STRATEGY_PERFEX_WINS = 'perfex_wins';
    const CONFLICT_STRATEGY_MOLONI_WINS = 'moloni_wins';
    
    public function __construct()
    {
        $this->CI = &get_instance();
        $this->CI->load->model('desk_moloni_model');
        $this->CI->load->model('estimates_model');
        
        $this->model = $this->CI->desk_moloni_model;
        $this->api_client = new MoloniApiClient();
        $this->entity_mapping = new EntityMappingService();
        $this->error_handler = new ErrorHandler();
        $this->client_sync = new ClientSyncService();
        $this->product_sync = new ProductSyncService();
        
        log_activity('EstimateSyncService initialized');
    }
    
    /**
     * Sync estimate from Perfex to Moloni
     * 
     * @param int $perfex_estimate_id
     * @param bool $force_update
     * @param array $additional_data
     * @return array
     */
    public function sync_perfex_to_moloni($perfex_estimate_id, $force_update = false, $additional_data = [])
    {
        $start_time = microtime(true);
        
        try {
            // Get Perfex estimate data
            $perfex_estimate = $this->get_perfex_estimate($perfex_estimate_id);
            if (!$perfex_estimate) {
                throw new \Exception("Perfex estimate ID {$perfex_estimate_id} not found");
            }
            
            // Check existing mapping
            $mapping = $this->entity_mapping->get_mapping_by_perfex_id(
                EntityMappingService::ENTITY_ESTIMATE,
                $perfex_estimate_id
            );
            
            // Validate sync conditions
            if (!$this->should_sync_to_moloni($mapping, $force_update)) {
                return [
                    'success' => true,
                    'message' => 'Estimate already synced and up to date',
                    'mapping_id' => $mapping ? $mapping->id : null,
                    'moloni_estimate_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);
                }
            }
            
            // Ensure client is synced first
            $client_result = $this->ensure_client_synced($perfex_estimate);
            if (!$client_result['success']) {
                throw new \Exception("Failed to sync client: " . $client_result['message']);
            }
            
            // Sync estimate items/products
            $products_result = $this->sync_estimate_products($perfex_estimate);
            if (!$products_result['success']) {
                log_message('warning', "Some products failed to sync for estimate {$perfex_estimate_id}: " . $products_result['message']);
            }
            
            // Transform Perfex data to Moloni format
            $moloni_data = $this->map_perfex_to_moloni_estimate($perfex_estimate, $additional_data);
            
            // Create or update estimate in Moloni
            $moloni_result = $this->create_or_update_moloni_estimate($moloni_data, $mapping);
            
            if (!$moloni_result['success']) {
                throw new \Exception("Moloni API error: " . $moloni_result['message']);
            }
            
            $moloni_estimate_id = $moloni_result['estimate_id'];
            $action = $moloni_result['action'];
            
            // Update or create mapping
            $mapping_id = $this->update_or_create_mapping(
                EntityMappingService::ENTITY_ESTIMATE,
                $perfex_estimate_id,
                $moloni_estimate_id,
                EntityMappingService::DIRECTION_PERFEX_TO_MOLONI,
                $mapping
            );
            
            // Log sync activity
            $execution_time = microtime(true) - $start_time;
            $this->log_sync_activity([
                'entity_type' => 'estimate',
                'entity_id' => $perfex_estimate_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_estimate),
                'moloni_data_hash' => $this->calculate_data_hash($moloni_result['data'] ?? [])
            ]);
            
            return [
                'success' => true,
                'message' => "Estimate {$action}d successfully in Moloni",
                'mapping_id' => $mapping_id,
                'moloni_estimate_id' => $moloni_estimate_id,
                'action' => $action,
                'execution_time' => $execution_time,
                'data_changes' => $this->detect_data_changes($perfex_estimate, $moloni_result['data'] ?? [])
            ];
            
        } catch (\Exception $e) {
            return $this->handle_sync_error($e, [
                'entity_type' => 'estimate',
                'entity_id' => $perfex_estimate_id,
                'direction' => 'perfex_to_moloni',
                'execution_time' => microtime(true) - $start_time,
                'mapping' => $mapping ?? null
            ]);
        }
    }
    
    /**
     * Sync estimate from Moloni to Perfex
     * 
     * @param int $moloni_estimate_id
     * @param bool $force_update
     * @param array $additional_data
     * @return array
     */
    public function sync_moloni_to_perfex($moloni_estimate_id, $force_update = false, $additional_data = [])
    {
        $start_time = microtime(true);
        
        try {
            // Get Moloni estimate data
            $moloni_response = $this->api_client->get_estimate($moloni_estimate_id);
            if (!$moloni_response['success']) {
                throw new \Exception("Moloni estimate ID {$moloni_estimate_id} not found: " . $moloni_response['message']);
            }
            
            $moloni_estimate = $moloni_response['data'];
            
            // Check existing mapping
            $mapping = $this->entity_mapping->get_mapping_by_moloni_id(
                EntityMappingService::ENTITY_ESTIMATE,
                $moloni_estimate_id
            );
            
            // Validate sync conditions
            if (!$this->should_sync_to_perfex($mapping, $force_update)) {
                return [
                    'success' => true,
                    'message' => 'Estimate already synced and up to date',
                    'mapping_id' => $mapping ? $mapping->id : null,
                    'perfex_estimate_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);
                }
            }
            
            // Ensure client is synced first
            $client_result = $this->ensure_moloni_client_synced($moloni_estimate);
            if (!$client_result['success']) {
                throw new \Exception("Failed to sync client: " . $client_result['message']);
            }
            
            // Transform Moloni data to Perfex format
            $perfex_data = $this->map_moloni_to_perfex_estimate($moloni_estimate, $additional_data);
            
            // Create or update estimate in Perfex
            $perfex_result = $this->create_or_update_perfex_estimate($perfex_data, $mapping);
            
            if (!$perfex_result['success']) {
                throw new \Exception("Perfex CRM error: " . $perfex_result['message']);
            }
            
            $perfex_estimate_id = $perfex_result['estimate_id'];
            $action = $perfex_result['action'];
            
            // Sync estimate items
            $this->sync_moloni_estimate_items($moloni_estimate, $perfex_estimate_id);
            
            // Update or create mapping
            $mapping_id = $this->update_or_create_mapping(
                EntityMappingService::ENTITY_ESTIMATE,
                $perfex_estimate_id,
                $moloni_estimate_id,
                EntityMappingService::DIRECTION_MOLONI_TO_PERFEX,
                $mapping
            );
            
            // Log sync activity
            $execution_time = microtime(true) - $start_time;
            $this->log_sync_activity([
                'entity_type' => 'estimate',
                'entity_id' => $perfex_estimate_id,
                'action' => $action,
                'direction' => 'moloni_to_perfex',
                'status' => 'success',
                'mapping_id' => $mapping_id,
                'request_data' => json_encode($moloni_estimate),
                'response_data' => json_encode($perfex_result),
                'processing_time' => $execution_time,
                'moloni_data_hash' => $this->calculate_data_hash($moloni_estimate),
                'perfex_data_hash' => $this->calculate_data_hash($perfex_result['data'] ?? [])
            ]);
            
            return [
                'success' => true,
                'message' => "Estimate {$action}d successfully in Perfex",
                'mapping_id' => $mapping_id,
                'perfex_estimate_id' => $perfex_estimate_id,
                'action' => $action,
                'execution_time' => $execution_time,
                'data_changes' => $this->detect_data_changes($moloni_estimate, $perfex_result['data'] ?? [])
            ];
            
        } catch (\Exception $e) {
            return $this->handle_sync_error($e, [
                'entity_type' => 'estimate',
                'entity_id' => $moloni_estimate_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_estimate = $this->get_perfex_estimate($mapping->perfex_id);
            $moloni_response = $this->api_client->get_estimate($mapping->moloni_id);
            
            if (!$perfex_estimate || !$moloni_response['success']) {
                return ['has_conflict' => false];
            }
            
            $moloni_estimate = $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_estimate_field_conflicts($perfex_estimate, $moloni_estimate);
                
                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 status conflicts
            if ($this->has_status_conflicts($perfex_estimate, $moloni_estimate)) {
                $conflicts['status_conflict'] = [
                    'perfex_status' => $perfex_estimate['status'],
                    'moloni_status' => $moloni_estimate['status'],
                    'message' => 'Estimate status differs between systems'
                ];
            }
            
            return [
                'has_conflict' => !empty($conflicts),
                'conflict_details' => $conflicts
            ];
            
        } catch (\Exception $e) {
            $this->error_handler->log_error('sync', 'ESTIMATE_CONFLICT_CHECK_FAILED', $e->getMessage(), [
                'mapping_id' => $mapping->id
            ]);
            
            return ['has_conflict' => false];
        }
    }
    
    /**
     * Map Perfex estimate to Moloni format
     * 
     * @param array $perfex_estimate
     * @param array $additional_data
     * @return array
     */
    protected function map_perfex_to_moloni_estimate($perfex_estimate, $additional_data = [])
    {
        // Get client mapping
        $client_mapping = $this->entity_mapping->get_mapping_by_perfex_id(
            EntityMappingService::ENTITY_CUSTOMER,
            $perfex_estimate['clientid']
        );
        
        if (!$client_mapping) {
            throw new \Exception("Client {$perfex_estimate['clientid']} must be synced before estimate sync");
        }
        
        // Get estimate items
        $estimate_items = $this->CI->estimates_model->get_estimate_items($perfex_estimate['id']);
        $moloni_products = [];
        
        foreach ($estimate_items as $item) {
            $moloni_products[] = $this->map_perfex_estimate_item_to_moloni($item);
        }
        
        $mapped_data = [
            'document_type' => $this->get_moloni_document_type($perfex_estimate),
            'customer_id' => $client_mapping->moloni_id,
            'document_set_id' => $this->get_default_document_set(),
            'date' => $perfex_estimate['date'],
            'expiration_date' => $perfex_estimate['expirydate'],
            'your_reference' => $perfex_estimate['estimate_number'],
            'our_reference' => $perfex_estimate['admin_note'] ?? '',
            'financial_discount' => (float)$perfex_estimate['discount_percent'],
            'special_discount' => (float)$perfex_estimate['discount_total'],
            'exchange_currency_id' => $this->convert_currency($perfex_estimate['currency'] ?? get_base_currency()->id),
            'exchange_rate' => 1.0,
            'notes' => $this->build_estimate_notes($perfex_estimate),
            'status' => $this->convert_perfex_status_to_moloni($perfex_estimate['status']),
            'products' => $moloni_products,
            'valid_until' => $perfex_estimate['expirydate']
        ];
        
        // Add tax summary
        $mapped_data['tax_exemption'] = $this->get_tax_exemption_reason($perfex_estimate);
        
        // Apply additional data overrides
        $mapped_data = array_merge($mapped_data, $additional_data);
        
        // Clean and validate data
        return $this->clean_moloni_estimate_data($mapped_data);
    }
    
    /**
     * Map Moloni estimate to Perfex format
     * 
     * @param array $moloni_estimate
     * @param array $additional_data
     * @return array
     */
    protected function map_moloni_to_perfex_estimate($moloni_estimate, $additional_data = [])
    {
        // Get client mapping
        $client_mapping = $this->entity_mapping->get_mapping_by_moloni_id(
            EntityMappingService::ENTITY_CUSTOMER,
            $moloni_estimate['customer_id']
        );
        
        if (!$client_mapping) {
            throw new \Exception("Customer {$moloni_estimate['customer_id']} must be synced before estimate sync");
        }
        
        $mapped_data = [
            'clientid' => $client_mapping->perfex_id,
            'number' => $moloni_estimate['document_number'] ?? '',
            'date' => $moloni_estimate['date'],
            'expirydate' => $moloni_estimate['valid_until'] ?? $moloni_estimate['expiration_date'],
            'currency' => $this->convert_moloni_currency_to_perfex($moloni_estimate['exchange_currency_id']),
            'subtotal' => (float)$moloni_estimate['net_value'],
            'total_tax' => (float)$moloni_estimate['tax_value'],
            'total' => (float)$moloni_estimate['gross_value'],
            'discount_percent' => (float)$moloni_estimate['financial_discount'],
            'discount_total' => (float)$moloni_estimate['special_discount'],
            'status' => $this->convert_moloni_status_to_perfex($moloni_estimate['status']),
            'adminnote' => $moloni_estimate['our_reference'] ?? '',
            'clientnote' => $moloni_estimate['notes'] ?? ''
        ];
        
        // Apply additional data overrides
        $mapped_data = array_merge($mapped_data, $additional_data);
        
        // Clean and validate data
        return $this->clean_perfex_estimate_data($mapped_data);
    }
    
    /**
     * Map Perfex estimate item to Moloni product format
     * 
     * @param array $item
     * @return array
     */
    protected function map_perfex_estimate_item_to_moloni($item)
    {
        // Try to get product mapping
        $product_mapping = null;
        if (!empty($item['rel_id']) && $item['rel_type'] === 'item') {
            $product_mapping = $this->entity_mapping->get_mapping_by_perfex_id(
                EntityMappingService::ENTITY_PRODUCT,
                $item['rel_id']
            );
        }
        
        return [
            'product_id' => $product_mapping ? $product_mapping->moloni_id : null,
            'name' => $item['description'],
            'summary' => $item['long_description'] ?? '',
            'qty' => (float)$item['qty'],
            'price' => (float)$item['rate'],
            'discount' => 0,
            'order' => (int)$item['item_order'],
            'exemption_reason' => '',
            'taxes' => $this->get_item_tax_data($item)
        ];
    }
    
    /**
     * Ensure client is synced before estimate sync
     * 
     * @param array $perfex_estimate
     * @return array
     */
    protected function ensure_client_synced($perfex_estimate)
    {
        $mapping = $this->entity_mapping->get_mapping_by_perfex_id(
            EntityMappingService::ENTITY_CUSTOMER,
            $perfex_estimate['clientid']
        );
        
        if (!$mapping) {
            // Sync client first
            return $this->client_sync->sync_perfex_to_moloni($perfex_estimate['clientid'], false);
        }
        
        return ['success' => true, 'message' => 'Client already synced'];
    }
    
    /**
     * Ensure Moloni client is synced
     * 
     * @param array $moloni_estimate
     * @return array
     */
    protected function ensure_moloni_client_synced($moloni_estimate)
    {
        $mapping = $this->entity_mapping->get_mapping_by_moloni_id(
            EntityMappingService::ENTITY_CUSTOMER,
            $moloni_estimate['customer_id']
        );
        
        if (!$mapping) {
            // Sync client first
            return $this->client_sync->sync_moloni_to_perfex($moloni_estimate['customer_id'], false);
        }
        
        return ['success' => true, 'message' => 'Client already synced'];
    }
    
    /**
     * Sync estimate products
     * 
     * @param array $perfex_estimate
     * @return array
     */
    protected function sync_estimate_products($perfex_estimate)
    {
        $results = ['success' => true, 'synced' => 0, 'errors' => []];
        
        $estimate_items = $this->CI->estimates_model->get_estimate_items($perfex_estimate['id']);
        
        foreach ($estimate_items as $item) {
            if (!empty($item['rel_id']) && $item['rel_type'] === 'item') {
                try {
                    $sync_result = $this->product_sync->sync_perfex_to_moloni($item['rel_id'], false);
                    if ($sync_result['success']) {
                        $results['synced']++;
                    } else {
                        $results['errors'][] = "Product {$item['rel_id']}: " . $sync_result['message'];
                    }
                } catch (\Exception $e) {
                    $results['errors'][] = "Product {$item['rel_id']}: " . $e->getMessage();
                }
            }
        }
        
        if (!empty($results['errors'])) {
            $results['success'] = false;
            $results['message'] = "Some products failed to sync: " . implode(', ', array_slice($results['errors'], 0, 3));
        }
        
        return $results;
    }
    
    /**
     * Create or update estimate in Moloni
     * 
     * @param array $moloni_data
     * @param object $mapping
     * @return array
     */
    protected function create_or_update_moloni_estimate($moloni_data, $mapping = null)
    {
        if ($mapping && $mapping->moloni_id) {
            // Update existing estimate
            $response = $this->api_client->update_estimate($mapping->moloni_id, $moloni_data);
            
            if ($response['success']) {
                return [
                    'success' => true,
                    'estimate_id' => $mapping->moloni_id,
                    'action' => 'update',
                    'data' => $response['data']
                ];
            }
        }
        
        // Create new estimate or fallback to create if update failed
        $response = $this->api_client->create_estimate($moloni_data);
        
        if ($response['success']) {
            return [
                'success' => true,
                'estimate_id' => $response['data']['document_id'],
                'action' => 'create',
                'data' => $response['data']
            ];
        }
        
        return [
            'success' => false,
            'message' => $response['message'] ?? 'Unknown error creating/updating estimate in Moloni'
        ];
    }
    
    /**
     * Create or update estimate in Perfex
     * 
     * @param array $perfex_data
     * @param object $mapping
     * @return array
     */
    protected function create_or_update_perfex_estimate($perfex_data, $mapping = null)
    {
        if ($mapping && $mapping->perfex_id) {
            // Update existing estimate
            $result = $this->CI->estimates_model->update($perfex_data, $mapping->perfex_id);
            
            if ($result) {
                return [
                    'success' => true,
                    'estimate_id' => $mapping->perfex_id,
                    'action' => 'update',
                    'data' => $perfex_data
                ];
            }
        }
        
        // Create new estimate or fallback to create if update failed
        $estimate_id = $this->CI->estimates_model->add($perfex_data);
        
        if ($estimate_id) {
            return [
                'success' => true,
                'estimate_id' => $estimate_id,
                'action' => 'create',
                'data' => $perfex_data
            ];
        }
        
        return [
            'success' => false,
            'message' => 'Failed to create/update estimate in Perfex CRM'
        ];
    }
    
    /**
     * Get Perfex estimate data
     * 
     * @param int $estimate_id
     * @return array|null
     */
    protected function get_perfex_estimate($estimate_id)
    {
        $estimate = $this->CI->estimates_model->get($estimate_id);
        return $estimate ? (array)$estimate : null;
    }
    
    /**
     * Convert Perfex status to Moloni status
     * 
     * @param int $perfex_status
     * @return string
     */
    protected function convert_perfex_status_to_moloni($perfex_status)
    {
        $status_mapping = [
            self::STATUS_DRAFT => 'draft',
            self::STATUS_SENT => 'sent', 
            self::STATUS_DECLINED => 'declined',
            self::STATUS_ACCEPTED => 'accepted',
            self::STATUS_EXPIRED => 'expired'
        ];
        
        return $status_mapping[$perfex_status] ?? 'draft';
    }
    
    /**
     * Convert Moloni status to Perfex status
     * 
     * @param string $moloni_status
     * @return int
     */
    protected function convert_moloni_status_to_perfex($moloni_status)
    {
        $status_mapping = [
            'draft' => self::STATUS_DRAFT,
            'sent' => self::STATUS_SENT,
            'declined' => self::STATUS_DECLINED,
            'accepted' => self::STATUS_ACCEPTED,
            'expired' => self::STATUS_EXPIRED
        ];
        
        return $status_mapping[$moloni_status] ?? self::STATUS_DRAFT;
    }
    
    /**
     * 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', 'ESTIMATE_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);
    }
    
    // Additional helper methods for specific estimate functionality...
    
    protected function should_sync_to_moloni($mapping, $force_update) { return true; }
    protected function should_sync_to_perfex($mapping, $force_update) { return true; }
    protected function handle_sync_conflict($mapping, $conflict_check) { return ['success' => false, 'message' => 'Conflict detected']; }
    protected function detect_data_changes($old_data, $new_data) { return []; }
    protected function update_or_create_mapping($entity_type, $perfex_id, $moloni_id, $direction, $mapping) { return 1; }
    protected function detect_estimate_field_conflicts($perfex_estimate, $moloni_estimate) { return []; }
    protected function has_status_conflicts($perfex_estimate, $moloni_estimate) { return false; }
    protected function get_moloni_document_type($perfex_estimate) { return self::MOLONI_DOC_TYPE_QUOTE; }
    protected function get_default_document_set() { return 1; }
    protected function convert_currency($currency_id) { return 1; }
    protected function build_estimate_notes($perfex_estimate) { return $perfex_estimate['clientnote'] ?? ''; }
    protected function get_tax_exemption_reason($perfex_estimate) { return ''; }
    protected function clean_moloni_estimate_data($data) { return $data; }
    protected function clean_perfex_estimate_data($data) { return $data; }
    protected function convert_moloni_currency_to_perfex($currency_id) { return 1; }
    protected function get_item_tax_data($item) { return []; }
    protected function sync_moloni_estimate_items($moloni_estimate, $perfex_estimate_id) { return true; }
    protected function get_perfex_modification_time($estimate_id) { return time(); }
    protected function get_moloni_modification_time($estimate_id) { return time(); }
}