<?php
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Librería para comunicación ISAPI con dispositivos Hikvision DS-K2622X
 * Maneja autenticación, lectura de eventos y control de puertas
 */
class Hikvision_isapi_library {
    
    private $CI;
    private $device_config;
    private $base_url;
    private $auth_header;
    private $timeout = 30;
    private $connect_timeout = 10;
    
    public function __construct() {
        $this->CI =& get_instance();
        $this->CI->load->helper('url');
        log_message('info', 'Hikvision ISAPI Library iniciada');
    }
    
    /**
     * Configura la conexión con el dispositivo
     * @param array $config - Configuración del dispositivo (ip, puerto, usuario, password)
     * @return bool
     */
    public function set_device_config($config) {
        if (!$this->_validate_config($config)) {
            return false;
        }
        
        $this->device_config = $config;
        $this->base_url = "http://{$config['ip_address']}:{$config['puerto']}/ISAPI/";
        
        // Configurar autenticación HTTP Digest
        $this->auth_header = base64_encode($config['usuario'] . ':' . $config['password']);
        
        log_message('debug', "Dispositivo configurado: {$config['ip_address']}:{$config['puerto']}");
        return true;
    }
    
    /**
     * Verifica la conectividad con el dispositivo
     * @return array
     */
    public function test_connection() {
        try {
            $response = $this->_make_request('System/deviceInfo', 'GET');
            
            if ($response['success']) {
                return [
                    'success' => true,
                    'message' => 'Conexión exitosa con el dispositivo',
                    'device_info' => $this->_parse_xml_response($response['data'])
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Error de conexión: ' . $response['error'],
                    'device_info' => null
                ];
            }
        } catch (Exception $e) {
            log_message('error', 'Error al probar conexión: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => 'Excepción: ' . $e->getMessage(),
                'device_info' => null
            ];
        }
    }
    
    /**
     * Obtiene eventos de acceso de la puerta 1 (lectura de TAGs)
     * @param string $start_time - Tiempo de inicio (Y-m-d\TH:i:s\Z)
     * @param string $end_time - Tiempo de fin (Y-m-d\TH:i:s\Z)
     * @return array
     */
    public function get_access_events($start_time = null, $end_time = null) {
        try {
            // Si no se especifican fechas, obtener eventos de la última hora
            if (!$start_time) {
                $start_time = date('Y-m-d\TH:i:s\Z', strtotime('-1 hour'));
            }
            if (!$end_time) {
                $end_time = date('Y-m-d\TH:i:s\Z');
            }
            
            $endpoint = "AccessControl/AcsEvent?format=json";
            $params = [
                'startTime' => $start_time,
                'endTime' => $end_time,
                'major' => 5,  // Tipo de evento de control de acceso
                'minor' => 75  // Evento de tarjeta
            ];
            
            $url_params = http_build_query($params);
            $response = $this->_make_request($endpoint . '&' . $url_params, 'GET');
            
            if ($response['success']) {
                $events = $this->_parse_access_events($response['data']);
                return [
                    'success' => true,
                    'events' => $events,
                    'total' => count($events)
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Error al obtener eventos: ' . $response['error'],
                    'events' => []
                ];
            }
        } catch (Exception $e) {
            log_message('error', 'Error al obtener eventos de acceso: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => 'Excepción: ' . $e->getMessage(),
                'events' => []
            ];
        }
    }
    
    /**
     * Abre la puerta 2 (control de barrera)
     * @param int $door_id - ID de la puerta (por defecto 2)
     * @return array
     */
    public function open_door($door_id = 2) {
        try {
            $endpoint = "AccessControl/RemoteControl/door/{$door_id}";
            
            $xml_data = '<?xml version="1.0" encoding="UTF-8"?>
            <RemoteControlDoor>
                <cmd>open</cmd>
            </RemoteControlDoor>';
            
            $response = $this->_make_request($endpoint, 'PUT', $xml_data);
            
            if ($response['success']) {
                log_message('info', "Puerta {$door_id} abierta exitosamente");
                return [
                    'success' => true,
                    'message' => "Puerta {$door_id} abierta correctamente",
                    'timestamp' => date('Y-m-d H:i:s')
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Error al abrir puerta: ' . $response['error']
                ];
            }
        } catch (Exception $e) {
            log_message('error', 'Error al abrir puerta: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => 'Excepción: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Obtiene el estado de las puertas
     * @return array
     */
    public function get_door_status() {
        try {
            $response = $this->_make_request('AccessControl/Door', 'GET');
            
            if ($response['success']) {
                $doors_data = $this->_parse_xml_response($response['data']);
                return [
                    'success' => true,
                    'doors' => $doors_data
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Error al obtener estado de puertas: ' . $response['error']
                ];
            }
        } catch (Exception $e) {
            log_message('error', 'Error al obtener estado de puertas: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => 'Excepción: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Configura el modo de trabajo del dispositivo
     * @param string $mode - Modo: 'normal', 'free', 'forbidden'
     * @param int $door_id - ID de la puerta
     * @return array
     */
    public function set_door_mode($mode = 'normal', $door_id = 1) {
        try {
            $endpoint = "AccessControl/Door/{$door_id}/workingMode";
            
            $xml_data = '<?xml version="1.0" encoding="UTF-8"?>
            <DoorWorkingMode>
                <mode>' . $mode . '</mode>
            </DoorWorkingMode>';
            
            $response = $this->_make_request($endpoint, 'PUT', $xml_data);
            
            if ($response['success']) {
                return [
                    'success' => true,
                    'message' => "Modo de puerta {$door_id} configurado a: {$mode}"
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Error al configurar modo: ' . $response['error']
                ];
            }
        } catch (Exception $e) {
            log_message('error', 'Error al configurar modo de puerta: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => 'Excepción: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Realiza una petición HTTP al dispositivo Hikvision
     * @param string $endpoint
     * @param string $method
     * @param string $data
     * @return array
     */
    private function _make_request($endpoint, $method = 'GET', $data = null) {
        $url = $this->base_url . $endpoint;
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CONNECTTIMEOUT => $this->connect_timeout,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_HTTPAUTH => CURLAUTH_DIGEST,
            CURLOPT_USERPWD => $this->device_config['usuario'] . ':' . $this->device_config['password'],
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/xml',
                'Accept: application/xml'
            ],
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 3
        ]);
        
        if ($data && in_array($method, ['POST', 'PUT'])) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        
        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error) {
            log_message('error', "CURL Error: {$error} para URL: {$url}");
            return [
                'success' => false,
                'error' => $error,
                'data' => null,
                'http_code' => 0
            ];
        }
        
        if ($http_code >= 200 && $http_code < 300) {
            return [
                'success' => true,
                'error' => null,
                'data' => $response,
                'http_code' => $http_code
            ];
        } else {
            log_message('error', "HTTP Error {$http_code} para URL: {$url}");
            return [
                'success' => false,
                'error' => "HTTP {$http_code}",
                'data' => $response,
                'http_code' => $http_code
            ];
        }
    }
    
    /**
     * Parsea eventos de acceso desde la respuesta JSON/XML
     * @param string $response_data
     * @return array
     */
    private function _parse_access_events($response_data) {
        $events = [];
        
        try {
            // Primero intentar como JSON
            $json_data = json_decode($response_data, true);
            if (json_last_error() === JSON_ERROR_NONE && isset($json_data['AcsEventList'])) {
                foreach ($json_data['AcsEventList']['AcsEvent'] as $event) {
                    $events[] = [
                        'event_id' => $event['eventId'] ?? null,
                        'event_time' => $event['eventTime'] ?? null,
                        'door_id' => $event['doorId'] ?? null,
                        'card_no' => $event['cardNo'] ?? null,
                        'card_type' => $event['cardType'] ?? null,
                        'event_type' => $event['eventType'] ?? null,
                        'employee_no' => $event['employeeNoString'] ?? null,
                        'result' => $event['result'] ?? null
                    ];
                }
            } else {
                // Si no es JSON válido, intentar como XML
                $xml = simplexml_load_string($response_data);
                if ($xml !== false) {
                    foreach ($xml->AcsEvent as $event) {
                        $events[] = [
                            'event_id' => (string)$event->eventId,
                            'event_time' => (string)$event->eventTime,
                            'door_id' => (string)$event->doorId,
                            'card_no' => (string)$event->cardNo,
                            'card_type' => (string)$event->cardType,
                            'event_type' => (string)$event->eventType,
                            'employee_no' => (string)$event->employeeNoString,
                            'result' => (string)$event->result
                        ];
                    }
                }
            }
        } catch (Exception $e) {
            log_message('error', 'Error al parsear eventos: ' . $e->getMessage());
        }
        
        return $events;
    }
    
    /**
     * Parsea respuesta XML genérica
     * @param string $xml_data
     * @return array
     */
    private function _parse_xml_response($xml_data) {
        try {
            $xml = simplexml_load_string($xml_data);
            if ($xml !== false) {
                return json_decode(json_encode($xml), true);
            }
        } catch (Exception $e) {
            log_message('error', 'Error al parsear XML: ' . $e->getMessage());
        }
        return [];
    }
    
    /**
     * Valida la configuración del dispositivo
     * @param array $config
     * @return bool
     */
    private function _validate_config($config) {
        $required_fields = ['ip_address', 'puerto', 'usuario', 'password'];
        
        foreach ($required_fields as $field) {
            if (!isset($config[$field]) || empty($config[$field])) {
                log_message('error', "Campo requerido faltante en configuración: {$field}");
                return false;
            }
        }
        
        // Validar formato de IP
        if (!filter_var($config['ip_address'], FILTER_VALIDATE_IP)) {
            log_message('error', "Formato de IP inválido: {$config['ip_address']}");
            return false;
        }
        
        // Validar puerto
        if (!is_numeric($config['puerto']) || $config['puerto'] < 1 || $config['puerto'] > 65535) {
            log_message('error', "Puerto inválido: {$config['puerto']}");
            return false;
        }
        
        return true;
    }
    
    /**
     * Obtiene información del dispositivo
     * @return array
     */
    public function get_device_info() {
        try {
            $response = $this->_make_request('System/deviceInfo', 'GET');
            
            if ($response['success']) {
                $device_info = $this->_parse_xml_response($response['data']);
                return [
                    'success' => true,
                    'device_info' => $device_info
                ];
            } else {
                return [
                    'success' => false,
                    'message' => 'Error al obtener información: ' . $response['error']
                ];
            }
        } catch (Exception $e) {
            log_message('error', 'Error al obtener información del dispositivo: ' . $e->getMessage());
            return [
                'success' => false,
                'message' => 'Excepción: ' . $e->getMessage()
            ];
        }
    }
    
    /**
     * Resetea configuración
     */
    public function reset_config() {
        $this->device_config = null;
        $this->base_url = null;
        $this->auth_header = null;
    }
}