<?php
/**
 * IMAPHandler - lectura de correos y búsqueda por asuntos + alias.
 * Mejora aplicada:
 * - Usa asuntos_alias en la comparación.
 * - No depende únicamente de TO/CC/TEXT del IMAP.
 * - Busca correos recientes, valida que pertenezcan al correo consultado y recién devuelve el resultado.
 */

declare(strict_types=1);

require_once __DIR__ . '/lib/CryptoService.php';

class IMAPHandler
{
    private PDO $pdo;
    private string $lastError = '';

    /* AFENIXX-IMAP-ALIASES-V6 */
    private const SEARCH_WINDOW_MINUTES = 30;
    private const SEARCH_LIMIT = 1000;

    /**
     * Mensajes localizados directamente por el servidor IMAP
     * mediante TO, CC, BCC, TEXT o encabezados de reenvío.
     */
    private array $recipientMatchedMessageNumbers = [];

    public function __construct(PDO $pdo) { $this->pdo = $pdo; }

    public function getLastError(): string {
        return $this->lastError;
    }

    private function msys(string $clave, string $fallback): string {
        try {
            $q = $this->pdo->prepare("SELECT contenido FROM mensajes_sistema WHERE clave=?");
            $q->execute([$clave]);
            $r = $q->fetch(PDO::FETCH_ASSOC);
            return $r['contenido'] ?? $fallback;
        } catch(Throwable $e){ return $fallback; }
    }

    private function decryptIfNeeded(string $cipher): string {
        if ($cipher === '') return '';

        try {
            return CryptoService::decryptOrFail($cipher);
        } catch (\Throwable $e) {
            error_log('[IMAPHandler] No se pudo descifrar la credencial IMAP: ' . $e->getMessage());
            return '';
        }
    }

    private function makeMailbox(array $conf): string {
        $host  = trim((string)($conf['imap_host'] ?? ''));
        $port  = (int)($conf['imap_puerto'] ?? 993);
        $flags = trim((string)($conf['imap_flags'] ?? '/imap/ssl'));
        if ($host === '') throw new RuntimeException('El host IMAP no está configurado.');
        if ($port < 1) throw new RuntimeException('El puerto IMAP no es válido.');
        return sprintf('{%s:%d%s}INBOX', $host, $port, $flags !== '' ? $flags : '/imap/ssl');
    }

    private function clearImapErrors(): void {
        if (function_exists('imap_errors')) @imap_errors();
        if (function_exists('imap_alerts')) @imap_alerts();
    }

    private function isAuthenticationError(string $detail): bool {
        foreach (['AUTHENTICATIONFAILED', 'authentication failed', 'login failed', 'invalid credentials'] as $needle) {
            if (stripos($detail, $needle) !== false) return true;
        }
        return false;
    }

    private function connectionErrorMessage(string $detail, bool $isGmail): string {
        if ($this->isAuthenticationError($detail)) {
            return $isGmail
                ? 'Gmail rechazó la credencial del buzón. Guarda una contraseña de aplicación vigente.'
                : 'El servidor IMAP rechazó la contraseña guardada. Actualiza la credencial del buzón.';
        }

        if (stripos($detail, 'certificate') !== false
            || stripos($detail, 'self signed') !== false
            || stripos($detail, 'peer certificate') !== false) {
            return 'El certificado TLS del servidor IMAP no coincide con la configuración guardada.';
        }

        if (stripos($detail, "Can't connect") !== false
            || stripos($detail, 'timed out') !== false
            || stripos($detail, 'connection refused') !== false
            || stripos($detail, 'name or service not known') !== false) {
            return 'No fue posible alcanzar el servidor IMAP. Revisa host, puerto y SSL.';
        }

        return 'No fue posible conectar al buzón IMAP. Revisa su configuración.';
    }

    private function open(array $conf) {
        $this->lastError = '';

        if (!function_exists('imap_open')) {
            $this->lastError = 'La extensión IMAP no está habilitada en el servidor.';
            throw new RuntimeException($this->lastError);
        }

        $host = strtolower(trim((string)($conf['imap_host'] ?? '')));
        $user = strtolower(trim((string)($conf['imap_usuario'] ?? '')));
        $pass = $this->decryptIfNeeded((string)($conf['imap_clave'] ?? ''));
        $flags = trim((string)($conf['imap_flags'] ?? '/imap/ssl')) ?: '/imap/ssl';
        $provider = strtolower(trim((string)(($conf['plataforma'] ?? '') . ' ' . ($conf['nombre_proveedor'] ?? '') . ' ' . $host)));
        $isGmail = str_contains($provider, 'gmail') || $host === 'imap.gmail.com';
        $isCpanel = !$isGmail && (
            str_contains($provider, 'cpanel')
            || str_contains($provider, 'hosting')
            || str_starts_with($host, 'mail.')
        );

        if ($host === '' || $user === '' || $pass === '') {
            $this->lastError = 'La configuración o la credencial del buzón IMAP está incompleta.';
            throw new RuntimeException($this->lastError);
        }

        if (function_exists('imap_timeout')) {
            @imap_timeout(IMAP_OPENTIMEOUT, 15);
            @imap_timeout(IMAP_READTIMEOUT, 20);
        }

        $attempts = [$flags];
        if ($isCpanel && str_contains($flags, '/ssl') && !str_contains($flags, '/novalidate-cert')) {
            $attempts[] = $flags . '/novalidate-cert';
        }

        $lastDetail = 'Error desconocido';
        foreach ($attempts as $attemptFlags) {
            $this->clearImapErrors();
            $attemptConf = $conf;
            $attemptConf['imap_host'] = $host;
            $attemptConf['imap_flags'] = $attemptFlags;
            $mailbox = $this->makeMailbox($attemptConf);

            $stream = @imap_open($mailbox, $user, $pass, OP_READONLY, 1, [
                'DISABLE_AUTHENTICATOR' => 'GSSAPI',
            ]);

            if ($stream !== false) {
                $this->clearImapErrors();
                return $stream;
            }

            $errors = function_exists('imap_errors') ? (@imap_errors() ?: []) : [];
            $lastDetail = trim(implode(' | ', array_filter([
                (string)(imap_last_error() ?: ''),
                ...array_map('strval', is_array($errors) ? $errors : []),
            ]))) ?: 'Error desconocido';

            if ($this->isAuthenticationError($lastDetail)) break;
        }

        error_log(sprintf(
            '[IMAPHandler] Fallo de conexión para %s en %s: %s',
            $user,
            $host,
            $lastDetail
        ));

        $this->lastError = $this->connectionErrorMessage($lastDetail, $isGmail);
        throw new RuntimeException($this->lastError);
    }

    private function normalizeHtml(string $html): string {
        $html = preg_replace(['/<script\b[^>]*>(.*?)<\/script>/is', '/<iframe\b[^>]*>(.*?)<\/iframe>/is'], '', $html);
        $html = preg_replace('/<a\s+(?!.*\btarget=([\'\"]?_blank)\1)/i', '<a target="_blank" rel="noopener noreferrer" ', $html);
        $style = '<style>
            .email-wrapper { font-family: sans-serif; text-align: center; width: 100%; }
            .email-wrapper table, .email-wrapper tr, .email-wrapper td { border: none !important; border-collapse: collapse !important; }
            .email-wrapper table { margin-left: auto; margin-right: auto; }
            .email-wrapper img { max-width: 100%; height: auto; }
            .email-wrapper a { color: inherit; text-decoration: underline; }
        </style>';
        $meta_charset = '<meta charset="UTF-8">';
        return $meta_charset . $style . '<div class="email-wrapper">' . $html . '</div>';
    }

    /**
     * Recursively finds a specific MIME part within an email structure.
     */
    private function searchMimePart(object $structure, string $target_mimetype, string $section_prefix = ''): ?array {
        if (isset($structure->parts) && is_array($structure->parts)) {
            foreach ($structure->parts as $index => $part) {
                $current_section = ($section_prefix ? $section_prefix . '.' : '') . ($index + 1);

                $part_mimetype = '';
                if (isset($part->type) && is_int($part->type)) {
                    $primary_types = ["text", "multipart", "message", "application", "audio", "image", "video", "other"];
                    $primary = $primary_types[$part->type] ?? "other";
                    $secondary = isset($part->subtype) ? (string)$part->subtype : '';
                    $part_mimetype = $primary . "/" . $secondary;
                }

                if (!empty($part_mimetype) && strcasecmp($part_mimetype, $target_mimetype) === 0) {
                    return ['part' => $part, 'section' => $current_section];
                }

                if (isset($part->parts)) {
                    $result = $this->searchMimePart($part, $target_mimetype, $current_section);
                    if ($result) {
                        return $result;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Fetches the HTML content of an email, handling complex MIME structures.
     */
    private function fetchMessageHtml($stream, int $msgno): string {
        $structure = imap_fetchstructure($stream, $msgno);

        $partInfo = $structure ? $this->searchMimePart($structure, 'text/html') : null;
        $is_html = true;

        if (!$partInfo && $structure) {
            $partInfo = $this->searchMimePart($structure, 'text/plain');
            $is_html = false;
        }

        $body = '';
        $encoding = -1;

        if ($partInfo) {
            $part = $partInfo['part'];
            $section = $partInfo['section'];
            $body = (string)imap_fetchbody($stream, $msgno, $section);
            $encoding = $part->encoding ?? -1;
        } else {
            $body = (string)imap_body($stream, $msgno);
            $encoding = $structure->encoding ?? -1;
            $is_html = ($structure && isset($structure->subtype) && strcasecmp((string)$structure->subtype, 'HTML') === 0);
        }

        switch ($encoding) {
            case 3: $body = imap_base64($body); break;
            case 4: $body = quoted_printable_decode($body); break;
        }

        $detected = mb_detect_encoding($body, 'UTF-8, ISO-8859-1, ISO-8859-15, Windows-1252', true) ?: 'UTF-8';
        $body = mb_convert_encoding($body, 'UTF-8', $detected);

        $final_html = '';
        if ($is_html) {
            $final_html = $body;
        } else {
            $final_html = '<pre style="white-space:pre-wrap;text-align:left;font-family:monospace;">' . htmlspecialchars($body, ENT_QUOTES, 'UTF-8') . '</pre>';
        }

        return $this->normalizeHtml($final_html);
    }

    private function decodeSubject(string $subject): string {
        $decoded = @mb_decode_mimeheader(str_replace('_', ' ', $subject));
        if (!is_string($decoded) || trim($decoded) === '') {
            $decoded = $subject;
        }
        return trim(preg_replace('/\s+/u', ' ', $decoded) ?? $decoded);
    }

    private function normalizeText(string $value): string {
        $value = $this->decodeSubject($value);

        if (
            class_exists('\Normalizer')
        ) {
            $normalized = \Normalizer::normalize(
                $value,
                \Normalizer::FORM_C
            );

            if (is_string($normalized)) {
                $value = $normalized;
            }
        }

        $value = html_entity_decode(
            $value,
            ENT_QUOTES | ENT_HTML5,
            'UTF-8'
        );

        $value = str_replace(
            [
                "\u{200B}",
                "\u{200C}",
                "\u{200D}",
                "\u{FEFF}",
                '“',
                '”',
                '„',
                '’',
                '‘',
                '–',
                '—',
            ],
            [
                '',
                '',
                '',
                '',
                '"',
                '"',
                '"',
                "'",
                "'",
                '-',
                '-',
            ],
            $value
        );

        $value = preg_replace(
            '/\s+/u',
            ' ',
            trim($value)
        ) ?? trim($value);

        /*
         * Gmail puede agregar estos prefijos cuando el
         * correo fue reenviado manualmente.
         */
        $value = preg_replace(
            '/^(?:(?:re|rv|fw|fwd)\s*:\s*)+/iu',
            '',
            $value
        ) ?? $value;

        return mb_strtolower(
            trim($value),
            'UTF-8'
        );
    }

    private function loadAliasesMap(array $asuntos): array {
        $ids = [];
        foreach ($asuntos as $a) {
            if (isset($a['id'])) {
                $ids[] = (int)$a['id'];
            }
        }

        $ids = array_values(array_unique(array_filter($ids)));
        if (!$ids) return [];

        try {
            $placeholders = implode(',', array_fill(0, count($ids), '?'));
            $stmt = $this->pdo->prepare("
                SELECT id_asunto, nombre
                FROM asuntos_alias
                WHERE id_asunto IN ($placeholders)
                ORDER BY id ASC
            ");
            $stmt->execute($ids);

            $map = [];
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $id = (int)$row['id_asunto'];
                $nombre = trim((string)$row['nombre']);
                if ($nombre !== '') {
                    $map[$id][] = $nombre;
                }
            }
            return $map;
        } catch (\Throwable $e) {
            error_log('[IMAPHandler] Error cargando alias de asuntos: ' . $e->getMessage());
            return [];
        }
    }

    private function buildTermsForAsunto(array $asunto, array $aliasesMap): array {
        $id = (int)($asunto['id'] ?? 0);
        $terms = [];

        $principal = trim((string)($asunto['nombre'] ?? ''));
        if ($principal !== '') {
            $terms[] = ['tipo' => 'asunto', 'texto' => $principal];
        }

        foreach (($aliasesMap[$id] ?? []) as $alias) {
            $alias = trim((string)$alias);
            if ($alias !== '') {
                $terms[] = ['tipo' => 'alias', 'texto' => $alias];
            }
        }

        $seen = [];
        $unique = [];
        foreach ($terms as $term) {
            $key = $this->normalizeText($term['texto']);
            if ($key === '' || isset($seen[$key])) continue;
            $seen[$key] = true;
            $unique[] = $term;
        }
        return $unique;
    }

    private function isFlexibleAsunto(array $asunto): bool {
        if (!empty($asunto['es_flexible'])) return true;
        if (isset($asunto['modo_coincidencia']) && (string)$asunto['modo_coincidencia'] === 'flexible') return true;
        if (isset($asunto['match_type']) && in_array((string)$asunto['match_type'], ['contiene', 'flexible'], true)) return true;
        return false;
    }

    private function matchSubject(string $subject, array $asunto, array $aliasesMap): ?array {
        $subjectNorm = $this->normalizeText($subject);
        if ($subjectNorm === '') return null;

        $isFlexible = $this->isFlexibleAsunto($asunto);

        foreach ($this->buildTermsForAsunto($asunto, $aliasesMap) as $term) {
            $needle = $this->normalizeText($term['texto']);
            if ($needle === '') continue;

            $ok = $isFlexible
                ? mb_strpos($subjectNorm, $needle, 0, 'UTF-8') !== false
                : $subjectNorm === $needle;

            if ($ok) {
                return [
                    'tipo' => $term['tipo'],
                    'texto' => $term['texto'],
                    'modo' => $isFlexible ? 'flexible' : 'exacto'
                ];
            }
        }
        return null;
    }

    private function imapSearchEscape(
        string $value
    ): string {
        return str_replace(
            ['\\', '"'],
            ['\\\\', '\\"'],
            $value
        );
    }

    private function searchUids(
        $stream,
        string $query
    ): array {
        $uids = @imap_search(
            $stream,
            $query,
            SE_UID,
            'UTF-8'
        );

        /*
         * Algunos servidores cPanel no aceptan que se
         * especifique UTF-8 en imap_search.
         */
        if (!is_array($uids)) {
            $uids = @imap_search(
                $stream,
                $query,
                SE_UID
            );
        }

        return is_array($uids)
            ? array_map('intval', $uids)
            : [];
    }

    private function getCandidateMessageNumbers(
        $stream,
        string $correo,
        int $limit = self::SEARCH_LIMIT
    ): array {
        $this->recipientMatchedMessageNumbers = [];

        $correo = mb_strtolower(
            trim($correo),
            'UTF-8'
        );

        if ($correo === '') {
            return [];
        }

        /*
         * SINCE trabaja por fecha y no por minutos.
         * La validación exacta de los 30 minutos se hace
         * posteriormente usando la fecha real del mensaje.
         */
        $since = date(
            'd-M-Y',
            strtotime('-1 day')
        );

        $escapedCorreo =
            $this->imapSearchEscape($correo);

        $criteria = [
            'TO "' . $escapedCorreo . '"',
            'CC "' . $escapedCorreo . '"',
            'BCC "' . $escapedCorreo . '"',
            'TEXT "' . $escapedCorreo . '"',
            'HEADER "Delivered-To" "'
                . $escapedCorreo
                . '"',
            'HEADER "X-Original-To" "'
                . $escapedCorreo
                . '"',
            'HEADER "X-Forwarded-To" "'
                . $escapedCorreo
                . '"',
            'HEADER "Original-Recipient" "'
                . $escapedCorreo
                . '"',
        ];

        $targetUids = [];

        foreach ($criteria as $criterion) {
            $query =
                'SINCE "'
                . $since
                . '" '
                . $criterion;

            $targetUids = array_merge(
                $targetUids,
                $this->searchUids(
                    $stream,
                    $query
                )
            );
        }

        $targetUids = array_values(
            array_unique(
                array_filter(
                    array_map(
                        'intval',
                        $targetUids
                    )
                )
            )
        );

        /*
         * Cuando el servidor encontró el correo mediante
         * TO, CC, TEXT o un encabezado de reenvío, ese
         * resultado se considera validado por IMAP.
         */
        if ($targetUids !== []) {
            rsort(
                $targetUids,
                SORT_NUMERIC
            );

            if ($limit > 0) {
                $targetUids = array_slice(
                    $targetUids,
                    0,
                    $limit
                );
            }

            $messageNumbers = [];

            foreach ($targetUids as $uid) {
                $msgno = (int) @imap_msgno(
                    $stream,
                    $uid
                );

                if ($msgno < 1) {
                    continue;
                }

                $messageNumbers[] = $msgno;

                $this
                    ->recipientMatchedMessageNumbers[
                        $msgno
                    ] = true;
            }

            return array_values(
                array_unique(
                    $messageNumbers
                )
            );
        }

        /*
         * Respaldo para servidores que no soportan alguna
         * de las consultas anteriores.
         */
        $fallbackUids = $this->searchUids(
            $stream,
            'SINCE "' . $since . '"'
        );

        $fallbackUids = array_values(
            array_unique(
                array_filter(
                    array_map(
                        'intval',
                        $fallbackUids
                    )
                )
            )
        );

        rsort(
            $fallbackUids,
            SORT_NUMERIC
        );

        if ($limit > 0) {
            $fallbackUids = array_slice(
                $fallbackUids,
                0,
                $limit
            );
        }

        $messageNumbers = [];

        foreach ($fallbackUids as $uid) {
            $msgno = (int) @imap_msgno(
                $stream,
                $uid
            );

            if ($msgno > 0) {
                $messageNumbers[] = $msgno;
            }
        }

        return array_values(
            array_unique(
                $messageNumbers
            )
        );
    }

    private function extractAddressesFromObjects($addresses): array {
        $out = [];
        if (!is_array($addresses)) return $out;

        foreach ($addresses as $addr) {
            $mailbox = isset($addr->mailbox) ? (string)$addr->mailbox : '';
            $host = isset($addr->host) ? (string)$addr->host : '';
            if ($mailbox !== '' && $host !== '') {
                $out[] = mb_strtolower($mailbox . '@' . $host, 'UTF-8');
            }
        }
        return $out;
    }

    private function fetchPlainTextForSearch($stream, int $msgno): string {
        $structure = @imap_fetchstructure($stream, $msgno);
        if (!$structure) return '';

        $partInfo = $this->searchMimePart($structure, 'text/plain');
        $isHtml = false;

        if (!$partInfo) {
            $partInfo = $this->searchMimePart($structure, 'text/html');
            $isHtml = true;
        }

        $body = '';
        $encoding = $structure->encoding ?? -1;

        if ($partInfo) {
            $part = $partInfo['part'];
            $section = $partInfo['section'];
            $body = (string)@imap_fetchbody($stream, $msgno, $section);
            $encoding = $part->encoding ?? $encoding;
        } else {
            $body = (string)@imap_body($stream, $msgno);
        }

        switch ($encoding) {
            case 3: $body = imap_base64($body); break;
            case 4: $body = quoted_printable_decode($body); break;
        }

        $detected = mb_detect_encoding($body, 'UTF-8, ISO-8859-1, ISO-8859-15, Windows-1252', true) ?: 'UTF-8';
        $body = mb_convert_encoding($body, 'UTF-8', $detected);
        if ($isHtml) $body = strip_tags($body);

        return mb_strtolower($body, 'UTF-8');
    }

    private function messageBelongsToCorreo(
        $stream,
        int $msgno,
        $header,
        string $correo
    ): bool {
        $correo = mb_strtolower(
            trim($correo),
            'UTF-8'
        );

        if ($correo === '') {
            return false;
        }

        /*
         * El servidor IMAP ya encontró directamente este
         * mensaje usando TO, CC, TEXT o encabezados.
         */
        if (
            isset(
                $this
                    ->recipientMatchedMessageNumbers[
                        $msgno
                    ]
            )
        ) {
            return true;
        }

        $addresses = [];

        $addresses = array_merge(
            $addresses,
            $this->extractAddressesFromObjects(
                $header->to ?? null
            )
        );

        $addresses = array_merge(
            $addresses,
            $this->extractAddressesFromObjects(
                $header->cc ?? null
            )
        );

        $addresses = array_merge(
            $addresses,
            $this->extractAddressesFromObjects(
                $header->bcc ?? null
            )
        );

        $addresses = array_merge(
            $addresses,
            $this->extractAddressesFromObjects(
                $header->reply_to ?? null
            )
        );

        if (
            in_array(
                $correo,
                $addresses,
                true
            )
        ) {
            return true;
        }

        /*
         * Buscar en todos los encabezados reales.
         */
        $rawHeader = (string) @imap_fetchheader(
            $stream,
            $msgno,
            0
        );

        if (
            stripos(
                $rawHeader,
                $correo
            ) !== false
        ) {
            return true;
        }

        /*
         * Buscar en el cuerpo MIME completo. Esto resulta
         * especialmente importante para correos reenviados
         * desde Gmail, Outlook o cPanel.
         */
        $rawBody = (string) @imap_body(
            $stream,
            $msgno,
            FT_PEEK
        );

        $bodyVariants = [
            $rawBody,
            quoted_printable_decode(
                $rawBody
            ),
            str_ireplace(
                '=40',
                '@',
                $rawBody
            ),
        ];

        foreach ($bodyVariants as $variant) {
            if (
                stripos(
                    $variant,
                    $correo
                ) !== false
            ) {
                return true;
            }
        }

        /*
         * Última validación sobre el contenido de texto
         * decodificado.
         */
        $bodyForSearch =
            $this->fetchPlainTextForSearch(
                $stream,
                $msgno
            );

        return mb_strpos(
            $bodyForSearch,
            $correo,
            0,
            'UTF-8'
        ) !== false;
    }

    
    /**
     * AFENIXX-IMAP-AGE-V11
     * Devuelve un único límite para buscar y abrir correos.
     */
    private function consultaAntiguedadMinutos(): int
    {
        static $minutes = null;

        if ($minutes !== null) {
            return $minutes;
        }

        $minutes = 16;

        try {
            $query = $this->pdo->prepare(
                "SELECT valor
                 FROM configuracion_sistema
                 WHERE clave = ?
                 LIMIT 1"
            );

            $query->execute([
                'consulta_antiguedad_minutos',
            ]);

            $configured = (int) $query->fetchColumn();

            if ($configured >= 1 && $configured <= 120) {
                $minutes = $configured;
            }
        } catch (Throwable $exception) {
            $minutes = 16;
        }

        return $minutes;
    }
public function buscarTodosLosAsuntos(array $imapConf, string $correo, array $asuntos): array {
        $this->lastError = '';
        try {
            $stream = $this->open($imapConf);
        } catch (\Throwable $e) {
            if ($this->lastError === '') $this->lastError = $e->getMessage();
            error_log('[IMAPHandler] Consulta pública sin conexión IMAP: ' . $this->lastError);
            return [];
        }

        $aliasesMap = $this->loadAliasesMap($asuntos);
        $msgnos = $this->getCandidateMessageNumbers($stream, $correo, self::SEARCH_LIMIT);

        if (empty($msgnos)) {
            @imap_close($stream);
            return [];
        }

        $resultados_encontrados = [];
        $vistos = [];

        foreach ($msgnos as $msgno) {
            if (empty($msgno)) continue;

            $header = @imap_headerinfo($stream, $msgno);
            if (!$header) continue;

            $email_date_str = $header->date ?? null;
            $email_timestamp = $email_date_str ? strtotime($email_date_str) : 0;
            if ($email_timestamp > 0 && $email_timestamp < (time() - (self::SEARCH_WINDOW_MINUTES * 60))) continue;

            $subject = $this->decodeSubject((string)($header->subject ?? ''));
            if ($subject === '') continue;

            $matchesForMessage = [];
            foreach ($asuntos as $a) {
                $match = $this->matchSubject($subject, $a, $aliasesMap);
                if ($match !== null) {
                    $matchesForMessage[] = [$a, $match];
                }
            }

            if (!$matchesForMessage) continue;
            if (!$this->messageBelongsToCorreo($stream, (int)$msgno, $header, $correo)) continue;

            foreach ($matchesForMessage as [$a, $match]) {
                $key = (int)$a['id'] . '|' . $subject . '|' . $email_timestamp;
                if (isset($vistos[$key])) continue;
                $vistos[$key] = true;

                $resultados_encontrados[] = [
                    'asunto_id' => $a['id'],
                    'asunto_nombre' => $a['nombre'],
                    'fecha' => date('d/m/Y H:i', $email_timestamp),
                    'es_publico' => (isset($a['visible_publico']) && $a['visible_publico'] == 1),
                    'timestamp' => $email_timestamp,
                    'asunto_real' => $subject,
                    'match_tipo' => $match['tipo'],
                    'match_texto' => $match['texto'],
                    'match_modo' => $match['modo']
                ];
            }
        }

        @imap_close($stream);

        if (!empty($resultados_encontrados)) {
            usort($resultados_encontrados, function($a, $b) {
                return $b['timestamp'] <=> $a['timestamp'];
            });
        }

        return $resultados_encontrados;
    }

    public function buscarUnAsunto(array $imapConf, string $correo, array $asuntos, int $id_asunto_especifico): array {
        $this->lastError = '';
        $asunto_a_buscar = null;
        foreach ($asuntos as $a) {
            if ((int)$a['id'] === $id_asunto_especifico) {
                $asunto_a_buscar = $a;
                break;
            }
        }

        if (!$asunto_a_buscar) {
            return ['ok' => false, 'title' => 'Error', 'message' => 'Asunto no válido.'];
        }

        try {
            $stream = $this->open($imapConf);
        } catch (\Throwable $e) {
            return ['ok' => false, 'title' => 'Error de Conexión IMAP', 'message' => $e->getMessage()];
        }

        $aliasesMap = $this->loadAliasesMap($asuntos);
        $msgnos = $this->getCandidateMessageNumbers($stream, $correo, self::SEARCH_LIMIT);

        if (empty($msgnos)) {
            @imap_close($stream);
            return [
                'ok' => false,
                'title' => $this->msys('API_TITULO_SIN_RESULTADOS', 'Sin resultados'),
                'message' => $this->msys('API_SIN_RESULTADOS', 'No se encontraron correos recientes.')
            ];
        }

        foreach ($msgnos as $msgno) {
            if (empty($msgno)) continue;

            $header = @imap_headerinfo($stream, $msgno);
            if (!$header) continue;

            $email_date_str = $header->date ?? null;
            $email_timestamp = $email_date_str ? strtotime($email_date_str) : 0;
            if ($email_timestamp > 0 && $email_timestamp < (time() - (self::SEARCH_WINDOW_MINUTES * 60))) continue;

            $subject = $this->decodeSubject((string)($header->subject ?? ''));
            $match = $this->matchSubject($subject, $asunto_a_buscar, $aliasesMap);

            if ($match === null) continue;
            if (!$this->messageBelongsToCorreo($stream, (int)$msgno, $header, $correo)) continue;

            $html = $this->fetchMessageHtml($stream, (int)$msgno);
            @imap_close($stream);

            return [
                'ok' => true,
                'title' => '¡Listo!',
                'message' => 'Correo encontrado.',
                'data' => [
                    'date' => $email_date_str,
                    'subject' => $subject,
                    'html' => $html,
                    'match_tipo' => $match['tipo'],
                    'match_texto' => $match['texto'],
                    'match_modo' => $match['modo']
                ]
            ];
        }

        @imap_close($stream);
        return ['ok' => false, 'title' => 'Sin Resultados', 'message' => 'El correo que buscas ya no está disponible, expiró o no coincide con los asuntos/alias configurados.'];
    }
}
