<?php
/**
 * NEXUS IT — Email-to-Ticket (IMAP)
 * Lee correos no leídos y crea tickets automáticamente.
 * Compatible con Zoho Mail, Gmail, y cualquier servidor IMAP.
 */
class EmailToTicket {

    private static function getConfig(): array {
        $db = getDB();
        $stmt = $db->query("SELECT clave, valor FROM configuracion WHERE grupo = 'email_to_ticket'");
        $config = [];
        foreach ($stmt->fetchAll() as $row) {
            $config[$row['clave']] = $row['valor'];
        }
        return $config;
    }

    /**
     * Procesar correos no leídos y crear tickets
     */
    public static function procesar(): array {
        $config = self::getConfig();
        
        $host    = $config['imap_host'] ?? '';
        $port    = (int)($config['imap_port'] ?? 993);
        $user    = $config['imap_user'] ?? '';
        $pass    = $config['imap_password'] ?? '';
        $ssl     = ($config['imap_ssl'] ?? '1') === '1';
        $folder  = $config['imap_folder'] ?? 'INBOX';
        $enabled = ($config['imap_enabled'] ?? '0') === '1';

        if (!$enabled) {
            return ['ok' => false, 'message' => 'Email-to-Ticket deshabilitado'];
        }

        if (empty($host) || empty($user) || empty($pass)) {
            return ['ok' => false, 'message' => 'Configuración IMAP incompleta'];
        }

        // Construir string de conexión IMAP
        $sslFlag = $ssl ? '/ssl' : '';
        $mailbox = "{{$host}:{$port}/imap{$sslFlag}/novalidate-cert}{$folder}";

        $log = [];
        $log[] = "Conectando a: {$host}:{$port} (SSL: " . ($ssl ? 'sí' : 'no') . ")";

        // Conectar
        $imap = @imap_open($mailbox, $user, $pass);
        if (!$imap) {
            $error = imap_last_error();
            $log[] = "Error IMAP: $error";
            return ['ok' => false, 'message' => "Error de conexión IMAP: $error", 'log' => $log];
        }

        $log[] = "Conectado exitosamente";

        // Buscar correos no leídos
        $emails = imap_search($imap, 'UNSEEN');
        if (!$emails) {
            imap_close($imap);
            $log[] = "No hay correos nuevos";
            return ['ok' => true, 'message' => 'No hay correos nuevos', 'tickets_creados' => 0, 'log' => $log];
        }

        $log[] = count($emails) . " correo(s) no leído(s) encontrado(s)";

        $db = getDB();
        $ticketsCreados = 0;

        foreach ($emails as $emailNum) {
            try {
                // Obtener headers
                $header = imap_headerinfo($imap, $emailNum);
                $from = $header->from[0]->mailbox . '@' . $header->from[0]->host;
                $fromName = isset($header->from[0]->personal) 
                    ? self::decodeMime($header->from[0]->personal) 
                    : $from;
                $subject = isset($header->subject) ? self::decodeMime($header->subject) : '(Sin asunto)';
                $date = isset($header->date) ? date('Y-m-d H:i:s', strtotime($header->date)) : date('Y-m-d H:i:s');

                // Obtener cuerpo
                $body = self::getBody($imap, $emailNum);

                // Verificar si ya existe un ticket con este Message-ID (evitar duplicados)
                $messageId = isset($header->message_id) ? trim($header->message_id) : null;
                if ($messageId) {
                    $exists = $db->prepare("SELECT id FROM tickets WHERE email_message_id = ?");
                    $exists->execute([$messageId]);
                    if ($exists->fetch()) {
                        $log[] = "Duplicado omitido: $subject (de: $from)";
                        imap_setflag_full($imap, (string)$emailNum, "\\Seen");
                        continue;
                    }
                }

                // Verificar si es una respuesta a un ticket existente (Re: [TK250001] ...)
                if (preg_match('/\[?(TK\d{6,})\]?/', $subject, $matches)) {
                    $numTicket = $matches[1];
                    $ticket = $db->prepare("SELECT id, solicitante_id, estado, cerrado_at, resuelto_at, updated_at, tecnico_asignado_id, numero_ticket, titulo, prioridad, categoria_id, reaberturas FROM tickets WHERE numero_ticket = ?");
                    $ticket->execute([$numTicket]);
                    $ticketData = $ticket->fetch();
                    
                    if ($ticketData) {
                        $autorId = self::findOrCreateUser($db, $from, $fromName);

                        // Si el ticket está cerrado/resuelto → intentar reabrir
                        if (in_array($ticketData['estado'], ['cerrado', 'resuelto'])) {
                            // Verificar si la reapertura está habilitada
                            $permitir = 'true';
                            $diasLimite = 7;
                            try {
                                $cfgP = $db->prepare("SELECT valor FROM configuracion WHERE clave = 'ticket_permitir_reabrir'");
                                $cfgP->execute();
                                $permitir = $cfgP->fetchColumn() ?: 'true';
                                $cfgD = $db->prepare("SELECT valor FROM configuracion WHERE clave = 'ticket_reabrir_dias'");
                                $cfgD->execute();
                                $diasVal = $cfgD->fetchColumn();
                                if ($diasVal) $diasLimite = (int)$diasVal;
                            } catch (\Exception $e) {}

                            $fechaCierre = $ticketData['cerrado_at'] ?: $ticketData['resuelto_at'] ?: $ticketData['updated_at'];
                            $diasTranscurridos = (time() - strtotime($fechaCierre)) / 86400;

                            if (($permitir === 'true' || $permitir === '1') && $diasTranscurridos <= $diasLimite) {
                                // REABRIR ticket
                                $reaberturas = ((int)($ticketData['reaberturas'] ?? 0)) + 1;
                                $db->prepare("UPDATE tickets SET estado = 'en_progreso', reabierto_at = NOW(), reaberturas = ?,
                                    resuelto_at = NULL, cerrado_at = NULL, calificacion = NULL, comentario_calificacion = NULL,
                                    updated_at = NOW() WHERE id = ?")
                                    ->execute([$reaberturas, $ticketData['id']]);

                                // Recalcular SLA
                                if (class_exists('SLACalculator')) {
                                    try {
                                        $slaH = SLACalculator::getHorasSLA($ticketData['prioridad'], $ticketData['categoria_id'] ? (int)$ticketData['categoria_id'] : null);
                                        $fechaV = SLACalculator::calcularVencimiento($slaH);
                                        $db->prepare("UPDATE tickets SET sla_horas = ?, fecha_vencimiento = ? WHERE id = ?")
                                            ->execute([$slaH, $fechaV, $ticketData['id']]);
                                    } catch (\Exception $e) {}
                                }

                                // Agregar comentario
                                $db->prepare("INSERT INTO ticket_comentarios (ticket_id, autor_id, comentario, es_interno) VALUES (?,?,?,0)")
                                    ->execute([$ticketData['id'], $autorId, "Ticket reabierto por respuesta de email (vez #{$reaberturas}).\n\n{$body}"]);

                                // Notificar al técnico
                                if ($ticketData['tecnico_asignado_id']) {
                                    $db->prepare("INSERT INTO notificaciones (usuario_id, titulo, mensaje, tipo, url_accion, referencia_tipo, referencia_id) VALUES (?,?,?,?,?,?,?)")
                                        ->execute([$ticketData['tecnico_asignado_id'], "Ticket reabierto: {$numTicket}", "El solicitante respondió al ticket cerrado. Reapertura #{$reaberturas}.", 'alerta', "/app/admin/ticket-detalle.html?id={$ticketData['id']}", 'ticket', $ticketData['id']]);
                                }

                                // Notificar admins
                                $admins = $db->query("SELECT id FROM usuarios WHERE rol = 'admin' AND activo = 1")->fetchAll();
                                foreach ($admins as $a) {
                                    $db->prepare("INSERT INTO notificaciones (usuario_id, titulo, mensaje, tipo, url_accion, referencia_tipo, referencia_id) VALUES (?,?,?,?,?,?,?)")
                                        ->execute([$a['id'], "Ticket reabierto por email: {$numTicket}", "Reapertura #{$reaberturas}", 'alerta', "/app/admin/ticket-detalle.html?id={$ticketData['id']}", 'ticket', $ticketData['id']]);
                                }

                                $log[] = "REABIERTO: {$numTicket} (vez #{$reaberturas}) por respuesta de {$from}";
                                imap_setflag_full($imap, (string)$emailNum, "\\Seen");
                                continue;
                            } else {
                                // Fuera de plazo → crear ticket nuevo vinculado
                                $log[] = "Ticket {$numTicket} cerrado hace " . round($diasTranscurridos) . " días — se creará ticket nuevo vinculado";
                                // No hacer continue, dejar que fluya a la creación normal
                                // pero guardaremos el ID padre para vincular
                                $ticketPadreId = (int)$ticketData['id'];
                            }
                        } else {
                            // Ticket abierto/en_progreso → agregar comentario normal
                            $db->prepare("INSERT INTO ticket_comentarios (ticket_id, autor_id, comentario, es_interno) VALUES (?,?,?,0)")
                                ->execute([$ticketData['id'], $autorId, $body]);
                            $db->prepare("UPDATE tickets SET updated_at = NOW() WHERE id = ?")->execute([$ticketData['id']]);
                            
                            $log[] = "Comentario agregado a {$numTicket}: $subject (de: $from)";
                            imap_setflag_full($imap, (string)$emailNum, "\\Seen");
                            continue;
                        }
                    }
                }

                // Buscar o crear usuario
                $userId = self::findOrCreateUser($db, $from, $fromName);

                // Generar número de ticket
                $year = date('y'); $month = date('m');
                $seq = (int)$db->query("SELECT COUNT(*) FROM tickets WHERE YEAR(created_at)=YEAR(NOW()) AND MONTH(created_at)=MONTH(NOW())")->fetchColumn() + 1;
                $numero = 'TK' . $year . $month . str_pad($seq, 4, '0', STR_PAD_LEFT);

                // Detectar prioridad por palabras clave
                $prioridad = self::detectarPrioridad($subject . ' ' . $body);

                // ===== SLA =====
                $slaHoras = 24;
                $fechaVenc = date('Y-m-d H:i:s', strtotime('+24 hours'));
                if (class_exists('SLACalculator')) {
                    try {
                        $slaHoras = SLACalculator::getHorasSLA($prioridad, null);
                        $fechaVenc = SLACalculator::calcularVencimiento($slaHoras);
                    } catch (\Exception $e) {}
                }

                // ===== AUTO-ASIGNACIÓN =====
                $tecnicoId = null;
                $estado = 'abierto';
                try {
                    $autoStmt = $db->prepare("SELECT valor FROM configuracion WHERE clave = 'ticket_autoasignar'");
                    $autoStmt->execute();
                    $autoAsignar = $autoStmt->fetchColumn();
                    if ($autoAsignar === 'true' || $autoAsignar === '1') {
                        $tecnicos = $db->query("SELECT u.id, u.nombre_completo,
                            (SELECT COUNT(*) FROM tickets t WHERE t.tecnico_asignado_id = u.id AND t.estado IN ('abierto','en_progreso')) as tickets_abiertos
                            FROM usuarios u WHERE u.activo = 1 AND u.rol = 'tecnico'
                            ORDER BY tickets_abiertos ASC, u.id ASC")->fetchAll();
                        // Fallback a admins si no hay técnicos
                        if (count($tecnicos) === 0) {
                            $tecnicos = $db->query("SELECT u.id, u.nombre_completo,
                                (SELECT COUNT(*) FROM tickets t WHERE t.tecnico_asignado_id = u.id AND t.estado IN ('abierto','en_progreso')) as tickets_abiertos
                                FROM usuarios u WHERE u.activo = 1 AND u.rol = 'admin'
                                ORDER BY tickets_abiertos ASC, u.id ASC")->fetchAll();
                        }
                        if (count($tecnicos) > 0) {
                            $tecnicoId = (int)$tecnicos[0]['id'];
                            $estado = 'en_progreso';
                        }
                    }
                } catch (\Exception $e) {}

                // Crear ticket
                $stmt = $db->prepare("INSERT INTO tickets (numero_ticket, titulo, descripcion, solicitante_id, prioridad, estado, canal_origen, email_message_id, fecha_vencimiento, sla_horas, tecnico_asignado_id, ticket_padre_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)");
                $titulo = mb_substr($subject, 0, 200);
                $padreId = isset($ticketPadreId) ? $ticketPadreId : null;
                
                $stmt->execute([
                    $numero, $titulo, $body, $userId, $prioridad, $estado, 'email', $messageId, $fechaVenc, $slaHoras, $tecnicoId, $padreId
                ]);
                $ticketId = (int)$db->lastInsertId();

                if ($padreId) {
                    // Agregar comentario en ticket padre indicando que se creó uno nuevo
                    $db->prepare("INSERT INTO ticket_comentarios (ticket_id, autor_id, comentario, es_interno) VALUES (?,?,?,1)")
                        ->execute([$padreId, $userId, "Se creó un ticket de seguimiento: {$numero}"]);
                    $log[] = "  → Vinculado al ticket padre ID: {$padreId}";
                }

                // Log de actividad
                try {
                    $db->prepare("INSERT INTO log_actividad (usuario_id, accion, entidad_tipo, entidad_id, ip_address, descripcion) VALUES (?,?,?,?,?,?)")
                        ->execute([$userId, 'crear_ticket_email', 'tickets', $ticketId, '0.0.0.0', "Ticket creado desde email: $from"]);
                } catch (\Exception $e) {}

                // Notificación al solicitante
                try {
                    $db->prepare("INSERT INTO notificaciones (usuario_id, titulo, mensaje, tipo, url_accion, referencia_tipo, referencia_id) VALUES (?,?,?,?,?,?,?)")
                        ->execute([$userId, "Nuevo ticket por email: $numero", "Ticket creado desde correo de $from", 'ticket', "/app/admin/ticket-detalle.html?id=$ticketId", 'ticket', $ticketId]);
                } catch (\Exception $e) {}

                // Notificación al técnico asignado
                if ($tecnicoId) {
                    try {
                        $db->prepare("INSERT INTO notificaciones (usuario_id, titulo, mensaje, tipo, url_accion, referencia_tipo, referencia_id) VALUES (?,?,?,?,?,?,?)")
                            ->execute([$tecnicoId, "Ticket asignado: $numero", "Se te asignó automáticamente: \"{$titulo}\"", 'ticket', "/app/admin/ticket-detalle.html?id=$ticketId", 'ticket', $ticketId]);
                        // Email al técnico
                        if (class_exists('EmailService')) {
                            $stmtTec = $db->prepare("SELECT email, nombre_completo FROM usuarios WHERE id = ?"); $stmtTec->execute([$tecnicoId]); $tecData = $stmtTec->fetch();
                            if ($tecData) {
                                $ticketData = ['id' => $ticketId, 'numero_ticket' => $numero, 'titulo' => $titulo, 'prioridad' => $prioridad, 'estado' => $estado];
                                EmailService::notificarAsignacion($ticketData, $tecData['email'], $tecData['nombre_completo']);
                            }
                        }
                    } catch (\Exception $e) {}
                    $log[] = "  → Auto-asignado a técnico ID: $tecnicoId";
                }

                // Enviar confirmación por email al remitente
                if (class_exists('EmailService')) {
                    try {
                        $html = EmailService::template(
                            "Ticket Recibido: $numero",
                            "<p>Hola {$fromName},</p>
                            <p>Hemos recibido tu solicitud y se ha creado el ticket <strong>{$numero}</strong>.</p>
                            <p><strong>Asunto:</strong> {$titulo}</p>
                            <p><strong>Prioridad:</strong> {$prioridad}</p>
                            <p>Te notificaremos cuando haya una actualización.</p>
                            <p><em>Para dar seguimiento, responde a este correo incluyendo [{$numero}] en el asunto.</em></p>",
                            "",
                            ""
                        );
                        EmailService::enviar($from, "Nexus IT - Ticket Recibido {$numero}", $html);
                    } catch (\Exception $e) {
                        $log[] = "Error enviando confirmación a $from: " . $e->getMessage();
                    }
                }

                $ticketsCreados++;
                $log[] = "Ticket creado: {$numero} - \"{$titulo}\" (de: $from, prioridad: $prioridad)";

                // Clasificación IA
                if (class_exists('AIClassifier')) {
                    try {
                        $db2 = getDB();
                        $iaEnabled = $db2->prepare("SELECT valor FROM configuracion WHERE clave = 'ia_clasificacion_enabled'");
                        $iaEnabled->execute();
                        $iaOn = $iaEnabled->fetchColumn();
                        
                        if ($iaOn === '1' || $iaOn === 'true') {
                            $iaResult = AIClassifier::clasificar($titulo, $body, $ticketId);
                            if ($iaResult['ok']) {
                                $log[] = "  → IA: {$iaResult['categoria_nombre']}" . 
                                    ($iaResult['subcategoria_nombre'] ? " / {$iaResult['subcategoria_nombre']}" : '') .
                                    " | Prioridad: {$iaResult['prioridad']}";
                                if (!empty($iaResult['resumen'])) {
                                    $log[] = "  → Resumen: {$iaResult['resumen']}";
                                }
                            } else {
                                $log[] = "  → IA error: " . ($iaResult['error'] ?? 'desconocido');
                            }
                        }
                    } catch (\Exception $e) {
                        $log[] = "  → IA excepción: " . $e->getMessage();
                    }
                }


                // Marcar como leído
                imap_setflag_full($imap, (string)$emailNum, "\\Seen");

            } catch (\Exception $e) {
                $log[] = "Error procesando correo #{$emailNum}: " . $e->getMessage();
            }
        }

        imap_close($imap);

        return [
            'ok' => true,
            'message' => "Procesados: $ticketsCreados ticket(s) creado(s)",
            'tickets_creados' => $ticketsCreados,
            'log' => $log
        ];
    }

    /**
     * Buscar usuario por email o crear uno nuevo
     */
    private static function findOrCreateUser(PDO $db, string $email, string $name): int {
        $stmt = $db->prepare("SELECT id FROM usuarios WHERE email = ?");
        $stmt->execute([$email]);
        $user = $stmt->fetch();
        
        if ($user) return (int)$user['id'];

        // Crear usuario con rol 'usuario' y password aleatorio
        $hash = password_hash(bin2hex(random_bytes(16)), PASSWORD_BCRYPT);
        $db->prepare("INSERT INTO usuarios (uuid, nombre_completo, email, password_hash, rol, activo) VALUES (UUID(),?,?,?,?,1)")
            ->execute([$name ?: $email, $email, $hash, 'usuario']);
        
        return (int)$db->lastInsertId();
    }

    /**
     * Obtener cuerpo del email (texto plano preferido, fallback a HTML)
     */
    private static function getBody($imap, int $emailNum): string {
        $structure = imap_fetchstructure($imap, $emailNum);
        $body = '';

        if (empty($structure->parts)) {
            // Simple (no multipart)
            $body = imap_fetchbody($imap, $emailNum, '1');
            $body = self::decodeBody($body, $structure->encoding ?? 0);
            if (($structure->subtype ?? '') === 'HTML') {
                $body = self::htmlToText($body);
            }
        } else {
            // Multipart - buscar text/plain primero, luego text/html
            $textPart = '';
            $htmlPart = '';
            
            foreach ($structure->parts as $i => $part) {
                $partNum = (string)($i + 1);
                
                if ($part->type === 0) { // TEXT
                    $content = imap_fetchbody($imap, $emailNum, $partNum);
                    $content = self::decodeBody($content, $part->encoding ?? 0);
                    $charset = self::getCharset($part);
                    if ($charset && strtolower($charset) !== 'utf-8') {
                        $content = @mb_convert_encoding($content, 'UTF-8', $charset) ?: $content;
                    }
                    
                    if (strtoupper($part->subtype) === 'PLAIN') {
                        $textPart = $content;
                    } elseif (strtoupper($part->subtype) === 'HTML') {
                        $htmlPart = $content;
                    }
                }
                
                // Check nested multipart (e.g. multipart/alternative inside multipart/mixed)
                if ($part->type === 1 && !empty($part->parts)) {
                    foreach ($part->parts as $j => $subPart) {
                        $subPartNum = $partNum . '.' . ($j + 1);
                        if ($subPart->type === 0) {
                            $content = imap_fetchbody($imap, $emailNum, $subPartNum);
                            $content = self::decodeBody($content, $subPart->encoding ?? 0);
                            $charset = self::getCharset($subPart);
                            if ($charset && strtolower($charset) !== 'utf-8') {
                                $content = @mb_convert_encoding($content, 'UTF-8', $charset) ?: $content;
                            }
                            if (strtoupper($subPart->subtype) === 'PLAIN') {
                                $textPart = $content;
                            } elseif (strtoupper($subPart->subtype) === 'HTML') {
                                $htmlPart = $content;
                            }
                        }
                    }
                }
            }

            $body = !empty($textPart) ? $textPart : self::htmlToText($htmlPart);
        }

        // Limpiar y truncar
        $body = trim($body);
        if (mb_strlen($body) > 5000) {
            $body = mb_substr($body, 0, 5000) . "\n\n[... contenido truncado ...]";
        }

        return $body ?: '(Sin contenido)';
    }

    private static function decodeBody(string $body, int $encoding): string {
        switch ($encoding) {
            case 3: return base64_decode($body);       // BASE64
            case 4: return quoted_printable_decode($body); // QUOTED-PRINTABLE
            default: return $body;
        }
    }

    private static function getCharset($part): ?string {
        if (!empty($part->parameters)) {
            foreach ($part->parameters as $param) {
                if (strtolower($param->attribute) === 'charset') {
                    return $param->value;
                }
            }
        }
        return null;
    }

    private static function htmlToText(string $html): string {
        $html = preg_replace('/<br\s*\/?>/i', "\n", $html);
        $html = preg_replace('/<\/p>/i', "\n\n", $html);
        $html = preg_replace('/<\/div>/i', "\n", $html);
        $html = preg_replace('/<\/li>/i', "\n", $html);
        $html = strip_tags($html);
        $html = html_entity_decode($html, ENT_QUOTES, 'UTF-8');
        $html = preg_replace('/\n{3,}/', "\n\n", $html);
        return trim($html);
    }

    private static function decodeMime(string $str): string {
        $decoded = imap_mime_header_decode($str);
        $result = '';
        foreach ($decoded as $part) {
            $charset = ($part->charset === 'default') ? 'UTF-8' : $part->charset;
            $text = @mb_convert_encoding($part->text, 'UTF-8', $charset) ?: $part->text;
            $result .= $text;
        }
        return $result;
    }

    private static function detectarPrioridad(string $text): string {
        $text = mb_strtolower($text);
        $urgente = ['urgente', 'urgent', 'emergencia', 'emergency', 'crítico', 'critical', 'caído', 'down', 'no funciona', 'no sirve'];
        $alta = ['importante', 'important', 'alto', 'high', 'prioridad alta', 'asap', 'bloqueado', 'blocked'];
        
        foreach ($urgente as $word) {
            if (mb_strpos($text, $word) !== false) return 'urgente';
        }
        foreach ($alta as $word) {
            if (mb_strpos($text, $word) !== false) return 'alta';
        }
        return 'media';
    }

    /**
     * Test de conexión IMAP
     */
    public static function testConexion(): array {
        $config = self::getConfig();
        
        $host = $config['imap_host'] ?? '';
        $port = (int)($config['imap_port'] ?? 993);
        $user = $config['imap_user'] ?? '';
        $pass = $config['imap_password'] ?? '';
        $ssl  = ($config['imap_ssl'] ?? '1') === '1';

        if (empty($host) || empty($user) || empty($pass)) {
            return ['ok' => false, 'message' => 'Configuración IMAP incompleta'];
        }

        $sslFlag = $ssl ? '/ssl' : '';
        $mailbox = "{{$host}:{$port}/imap{$sslFlag}/novalidate-cert}INBOX";

        $imap = @imap_open($mailbox, $user, $pass);
        if (!$imap) {
            return ['ok' => false, 'message' => 'Error: ' . imap_last_error()];
        }

        $check = imap_check($imap);
        $unseen = imap_search($imap, 'UNSEEN');
        $numUnseen = $unseen ? count($unseen) : 0;

        imap_close($imap);

        return [
            'ok' => true,
            'message' => "Conectado. Buzón: {$check->Mailbox}, Total: {$check->Nmsgs} mensajes, No leídos: $numUnseen"
        ];
    }
}
