/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

<?php

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

/**
 * Document Access Control Library
 * Handles security and permissions for client document access
 * 
 * @package    Desk-Moloni
 * @version    3.0.0
 * @author     Descomplicar Business Solutions
 */
class DocumentAccessControl
{
    private $CI;
    private $cachePrefix = 'desk_moloni_access_';
    private $cacheTimeout = 300; // 5 minutes

    public function __construct()
    {
        $this->CI =& get_instance();
        
        // Load required models
        $this->CI->load->model('clients_model');
        $this->CI->load->model('invoices_model');
        $this->CI->load->model('estimates_model');
        
        // Initialize cache
        $this->CI->load->driver('cache');
    }

    /**
     * Check if client can access a specific document
     * 
     * @param int $clientId
     * @param int $documentId
     * @param string $documentType Optional document type for optimization
     * @return bool
     */
    public function canAccessDocument($clientId, $documentId, $documentType = null)
    {
        // Input validation
        if (!is_numeric($clientId) || !is_numeric($documentId) || $clientId <= 0 || $documentId <= 0) {
            return false;
        }

        // Check cache first
        $cacheKey = $this->cachePrefix . "doc_{$clientId}_{$documentId}";
        $cachedResult = $this->CI->cache->get($cacheKey);
        if ($cachedResult !== false) {
            return $cachedResult === 'allowed';
        }

        $hasAccess = false;

        try {
            // Verify client exists and is active
            if (!$this->_isClientActiveAndValid($clientId)) {
                $this->_cacheAccessResult($cacheKey, false);
                return false;
            }

            // If document type is specified, check only that type
            if ($documentType) {
                $hasAccess = $this->_checkDocumentTypeAccess($clientId, $documentId, $documentType);
            } else {
                // Check all document types
                $hasAccess = $this->_checkInvoiceAccess($clientId, $documentId) ||
                           $this->_checkEstimateAccess($clientId, $documentId) ||
                           $this->_checkCreditNoteAccess($clientId, $documentId) ||
                           $this->_checkReceiptAccess($clientId, $documentId);
            }

            // Cache the result
            $this->_cacheAccessResult($cacheKey, $hasAccess);

        } catch (Exception $e) {
            log_message('error', 'Document access control error: ' . $e->getMessage());
            $hasAccess = false;
        }

        return $hasAccess;
    }

    /**
     * Check if client can access multiple documents
     * 
     * @param int $clientId
     * @param array $documentIds
     * @return array Associative array [documentId => bool]
     */
    public function canAccessMultipleDocuments($clientId, array $documentIds)
    {
        $results = [];
        
        foreach ($documentIds as $documentId) {
            $results[$documentId] = $this->canAccessDocument($clientId, $documentId);
        }
        
        return $results;
    }

    /**
     * Get list of document IDs accessible by client
     * 
     * @param int $clientId
     * @param string $documentType Optional filter by document type
     * @param array $filters Optional additional filters
     * @return array
     */
    public function getAccessibleDocuments($clientId, $documentType = null, array $filters = [])
    {
        // Input validation
        if (!is_numeric($clientId) || $clientId <= 0) {
            return [];
        }

        // Check if client is valid
        if (!$this->_isClientActiveAndValid($clientId)) {
            return [];
        }

        $documentIds = [];

        try {
            if (!$documentType || $documentType === 'invoice') {
                $invoiceIds = $this->_getClientInvoiceIds($clientId, $filters);
                $documentIds = array_merge($documentIds, $invoiceIds);
            }

            if (!$documentType || $documentType === 'estimate') {
                $estimateIds = $this->_getClientEstimateIds($clientId, $filters);
                $documentIds = array_merge($documentIds, $estimateIds);
            }

            if (!$documentType || $documentType === 'credit_note') {
                $creditNoteIds = $this->_getClientCreditNoteIds($clientId, $filters);
                $documentIds = array_merge($documentIds, $creditNoteIds);
            }

            if (!$documentType || $documentType === 'receipt') {
                $receiptIds = $this->_getClientReceiptIds($clientId, $filters);
                $documentIds = array_merge($documentIds, $receiptIds);
            }

        } catch (Exception $e) {
            log_message('error', 'Get accessible documents error: ' . $e->getMessage());
            return [];
        }

        return array_unique($documentIds);
    }

    /**
     * Validate document access with detailed security checks
     * 
     * @param int $clientId
     * @param int $documentId
     * @param string $action Action being performed (view, download, etc.)
     * @return array Validation result with details
     */
    public function validateDocumentAccess($clientId, $documentId, $action = 'view')
    {
        $result = [
            'allowed' => false,
            'reason' => 'Access denied',
            'document_type' => null,
            'security_level' => 'standard'
        ];

        try {
            // Basic validation
            if (!is_numeric($clientId) || !is_numeric($documentId) || $clientId <= 0 || $documentId <= 0) {
                $result['reason'] = 'Invalid parameters';
                return $result;
            }

            // Check client validity
            if (!$this->_isClientActiveAndValid($clientId)) {
                $result['reason'] = 'Client not active or invalid';
                return $result;
            }

            // Check document existence and ownership
            $documentInfo = $this->_getDocumentInfo($documentId);
            if (!$documentInfo) {
                $result['reason'] = 'Document not found';
                return $result;
            }

            if ($documentInfo['client_id'] != $clientId) {
                $result['reason'] = 'Document does not belong to client';
                $this->_logSecurityViolation($clientId, $documentId, $action, 'ownership_violation');
                return $result;
            }

            // Check action permissions
            if (!$this->_isActionAllowed($documentInfo['type'], $action)) {
                $result['reason'] = 'Action not allowed for document type';
                return $result;
            }

            // Check document-specific security rules
            if (!$this->_checkDocumentSecurityRules($documentInfo, $action)) {
                $result['reason'] = 'Document security rules violation';
                return $result;
            }

            // All checks passed
            $result['allowed'] = true;
            $result['reason'] = 'Access granted';
            $result['document_type'] = $documentInfo['type'];
            $result['security_level'] = $this->_getDocumentSecurityLevel($documentInfo);

        } catch (Exception $e) {
            log_message('error', 'Document access validation error: ' . $e->getMessage());
            $result['reason'] = 'System error during validation';
        }

        return $result;
    }

    /**
     * Log security violation attempt
     * 
     * @param int $clientId
     * @param int $documentId
     * @param string $action
     * @param string $violationType
     */
    public function logSecurityViolation($clientId, $documentId, $action, $violationType)
    {
        $this->_logSecurityViolation($clientId, $documentId, $action, $violationType);
    }

    /**
     * Clear access cache for client
     * 
     * @param int $clientId
     */
    public function clearClientAccessCache($clientId)
    {
        // This would clear all cached access results for the client
        // Implementation depends on cache driver capabilities
        $pattern = $this->cachePrefix . "doc_{$clientId}_*";
        
        // For file cache, we'd need to scan and delete
        // For Redis, we could use pattern deletion
        // For now, we'll just document the intent
        log_message('info', "Access cache cleared for client {$clientId}");
    }

    // Private Methods

    /**
     * Check if client is active and valid
     */
    private function _isClientActiveAndValid($clientId)
    {
        $client = $this->CI->clients_model->get($clientId);
        return $client && $client['active'] == 1;
    }

    /**
     * Check access for specific document type
     */
    private function _checkDocumentTypeAccess($clientId, $documentId, $documentType)
    {
        switch ($documentType) {
            case 'invoice':
                return $this->_checkInvoiceAccess($clientId, $documentId);
            case 'estimate':
                return $this->_checkEstimateAccess($clientId, $documentId);
            case 'credit_note':
                return $this->_checkCreditNoteAccess($clientId, $documentId);
            case 'receipt':
                return $this->_checkReceiptAccess($clientId, $documentId);
            default:
                return false;
        }
    }

    /**
     * Check invoice access
     */
    private function _checkInvoiceAccess($clientId, $documentId)
    {
        $invoice = $this->CI->invoices_model->get($documentId);
        return $invoice && $invoice['clientid'] == $clientId;
    }

    /**
     * Check estimate access
     */
    private function _checkEstimateAccess($clientId, $documentId)
    {
        $estimate = $this->CI->estimates_model->get($documentId);
        return $estimate && $estimate['clientid'] == $clientId;
    }

    /**
     * Check credit note access
     */
    private function _checkCreditNoteAccess($clientId, $documentId)
    {
        // Credit notes in Perfex CRM are typically linked to invoices
        $creditNote = $this->CI->db->get_where('tblcreditnotes', ['id' => $documentId])->row_array();
        return $creditNote && $creditNote['clientid'] == $clientId;
    }

    /**
     * Check receipt access
     */
    private function _checkReceiptAccess($clientId, $documentId)
    {
        // Receipts are typically payment records in Perfex CRM
        $receipt = $this->CI->db->get_where('tblinvoicepaymentrecords', ['id' => $documentId])->row_array();
        if (!$receipt) {
            return false;
        }
        
        // Check if the payment belongs to an invoice owned by the client
        $invoice = $this->CI->invoices_model->get($receipt['invoiceid']);
        return $invoice && $invoice['clientid'] == $clientId;
    }

    /**
     * Cache access result
     */
    private function _cacheAccessResult($cacheKey, $hasAccess)
    {
        $value = $hasAccess ? 'allowed' : 'denied';
        $this->CI->cache->save($cacheKey, $value, $this->cacheTimeout);
    }

    /**
     * Get client invoice IDs
     */
    private function _getClientInvoiceIds($clientId, array $filters = [])
    {
        $this->CI->db->select('id');
        $this->CI->db->where('clientid', $clientId);
        
        // Apply filters
        if (isset($filters['status'])) {
            $this->CI->db->where('status', $filters['status']);
        }
        
        if (isset($filters['from_date'])) {
            $this->CI->db->where('date >=', $filters['from_date']);
        }
        
        if (isset($filters['to_date'])) {
            $this->CI->db->where('date <=', $filters['to_date']);
        }
        
        $query = $this->CI->db->get('tblinvoices');
        return array_column($query->result_array(), 'id');
    }

    /**
     * Get client estimate IDs
     */
    private function _getClientEstimateIds($clientId, array $filters = [])
    {
        $this->CI->db->select('id');
        $this->CI->db->where('clientid', $clientId);
        
        // Apply filters
        if (isset($filters['status'])) {
            $this->CI->db->where('status', $filters['status']);
        }
        
        if (isset($filters['from_date'])) {
            $this->CI->db->where('date >=', $filters['from_date']);
        }
        
        if (isset($filters['to_date'])) {
            $this->CI->db->where('date <=', $filters['to_date']);
        }
        
        $query = $this->CI->db->get('tblestimates');
        return array_column($query->result_array(), 'id');
    }

    /**
     * Get client credit note IDs
     */
    private function _getClientCreditNoteIds($clientId, array $filters = [])
    {
        $this->CI->db->select('id');
        $this->CI->db->where('clientid', $clientId);
        
        // Apply filters if table exists
        if ($this->CI->db->table_exists('tblcreditnotes')) {
            if (isset($filters['from_date'])) {
                $this->CI->db->where('date >=', $filters['from_date']);
            }
            
            if (isset($filters['to_date'])) {
                $this->CI->db->where('date <=', $filters['to_date']);
            }
            
            $query = $this->CI->db->get('tblcreditnotes');
            return array_column($query->result_array(), 'id');
        }
        
        return [];
    }

    /**
     * Get client receipt IDs
     */
    private function _getClientReceiptIds($clientId, array $filters = [])
    {
        // Get receipts through invoice payments
        $this->CI->db->select('tblinvoicepaymentrecords.id');
        $this->CI->db->join('tblinvoices', 'tblinvoices.id = tblinvoicepaymentrecords.invoiceid');
        $this->CI->db->where('tblinvoices.clientid', $clientId);
        
        // Apply filters
        if (isset($filters['from_date'])) {
            $this->CI->db->where('tblinvoicepaymentrecords.date >=', $filters['from_date']);
        }
        
        if (isset($filters['to_date'])) {
            $this->CI->db->where('tblinvoicepaymentrecords.date <=', $filters['to_date']);
        }
        
        $query = $this->CI->db->get('tblinvoicepaymentrecords');
        return array_column($query->result_array(), 'id');
    }

    /**
     * Get document information
     */
    private function _getDocumentInfo($documentId)
    {
        // Try to find document in different tables
        
        // Check invoices
        $invoice = $this->CI->db->get_where('tblinvoices', ['id' => $documentId])->row_array();
        if ($invoice) {
            return [
                'id' => $documentId,
                'type' => 'invoice',
                'client_id' => $invoice['clientid'],
                'status' => $invoice['status'],
                'data' => $invoice
            ];
        }
        
        // Check estimates
        $estimate = $this->CI->db->get_where('tblestimates', ['id' => $documentId])->row_array();
        if ($estimate) {
            return [
                'id' => $documentId,
                'type' => 'estimate',
                'client_id' => $estimate['clientid'],
                'status' => $estimate['status'],
                'data' => $estimate
            ];
        }
        
        // Check credit notes
        if ($this->CI->db->table_exists('tblcreditnotes')) {
            $creditNote = $this->CI->db->get_where('tblcreditnotes', ['id' => $documentId])->row_array();
            if ($creditNote) {
                return [
                    'id' => $documentId,
                    'type' => 'credit_note',
                    'client_id' => $creditNote['clientid'],
                    'status' => $creditNote['status'] ?? 'active',
                    'data' => $creditNote
                ];
            }
        }
        
        // Check receipts (payment records)
        $receipt = $this->CI->db->get_where('tblinvoicepaymentrecords', ['id' => $documentId])->row_array();
        if ($receipt) {
            // Get client ID from associated invoice
            $invoice = $this->CI->db->get_where('tblinvoices', ['id' => $receipt['invoiceid']])->row_array();
            if ($invoice) {
                return [
                    'id' => $documentId,
                    'type' => 'receipt',
                    'client_id' => $invoice['clientid'],
                    'status' => 'paid',
                    'data' => $receipt
                ];
            }
        }
        
        return null;
    }

    /**
     * Check if action is allowed for document type
     */
    private function _isActionAllowed($documentType, $action)
    {
        $allowedActions = [
            'invoice' => ['view', 'download', 'print'],
            'estimate' => ['view', 'download', 'print'],
            'credit_note' => ['view', 'download', 'print'],
            'receipt' => ['view', 'download', 'print']
        ];
        
        return isset($allowedActions[$documentType]) && 
               in_array($action, $allowedActions[$documentType]);
    }

    /**
     * Check document-specific security rules
     */
    private function _checkDocumentSecurityRules($documentInfo, $action)
    {
        // Example security rules:
        
        // Draft documents may have restricted access
        if ($documentInfo['type'] === 'estimate' && $documentInfo['status'] == 1) {
            // Draft estimate - only allow view
            return $action === 'view';
        }
        
        // Cancelled documents may be read-only
        if (isset($documentInfo['data']['status']) && $documentInfo['data']['status'] == 5) {
            // Cancelled - only allow view
            return $action === 'view';
        }
        
        // All other cases are allowed by default
        return true;
    }

    /**
     * Get document security level
     */
    private function _getDocumentSecurityLevel($documentInfo)
    {
        // Determine security level based on document properties
        if ($documentInfo['type'] === 'invoice' && 
            isset($documentInfo['data']['total']) && 
            $documentInfo['data']['total'] > 10000) {
            return 'high'; // High-value invoices
        }
        
        return 'standard';
    }

    /**
     * Log security violation
     */
    private function _logSecurityViolation($clientId, $documentId, $action, $violationType)
    {
        $logData = [
            'client_id' => $clientId,
            'document_id' => $documentId,
            'action' => $action,
            'violation_type' => $violationType,
            'ip_address' => $this->CI->input->ip_address(),
            'user_agent' => $this->CI->input->user_agent(),
            'timestamp' => date('Y-m-d H:i:s')
        ];
        
        // Log to system log
        log_message('warning', 'Security violation: ' . json_encode($logData));
        
        // Could also save to database security log table if it exists
        if ($this->CI->db->table_exists('tblsecurity_violations')) {
            $this->CI->db->insert('tblsecurity_violations', $logData);
        }
    }
}