import os
# Define para não procurar GPU; deve vir antes de 'import spacy' ou 'torch'
os.environ["CUDA_VISIBLE_DEVICES"] = "" 

import re
import sys
import json
import time
import email
import imaplib
import smtplib
import tempfile
import logging
import mimetypes
import io
import subprocess
import shutil
from pathlib import Path
from typing import Tuple, List, Dict, Optional
from email.header import decode_header, make_header
from email.message import EmailMessage
from email.utils import parseaddr, make_msgid
from email.mime.text import MIMEText
from email.mime.image import MIMEImage

from dotenv import load_dotenv
from docx import Document
from docx.shared import Cm, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.text import WD_TAB_ALIGNMENT

# Importações para a função de extração robusta
from docx.oxml.text.paragraph import CT_P
from docx.oxml.table import CT_Tbl
# --- REMOVIDO: from docx.oxml.sdt import CT_Sdt ---
from docx.table import Table
from docx.text.paragraph import Paragraph

# Bloco OpenAI
try:
    from openai import OpenAI, APIError
except ImportError:
    OpenAI = None
    APIError = None

# Bloco LanguageTool
try:
    import language_tool_python
except ImportError:
    language_tool_python = None

# ==============================
# Constantes e Configurações
# ==============================
PALAVRAS_PERSONALIZADAS = [
    'multicomputador', 'multiprocessador', 'Flynn', 'telemóvel', 'hardware',
    'software', 'backend', 'frontend', 'framework', 'INSUTEC', 'EISI',
    'Arquitetura', 'Computadores', 'SQL', 'script',
    'Rouget', 'Ruano', 'BFS', 'DFS', 'A*', 'gestão', 'Epifania', 'Rodrigues',
    'algoritmo.py', 'desempenho.py', 'Debruçe',
    'lab.ed-consulting.ao', 'lab.insutec.ao', 'mnt', 'ed', 'consulting'
]
LISTA_PALAVRAS_INAPROPRIADAS = [
    'merda', 'porra', 'caralho', 'foder', 'puta', 'cona',
    'grosseira', 'estúpido', 'idiota'
]
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] - %(message)s",
    handlers=[logging.FileHandler("app.log", encoding='utf-8'), logging.StreamHandler(sys.stdout)]
)
load_dotenv()
# (Restante das suas constantes... sem alteração)
IMAP_HOST = os.getenv("IMAP_HOST", "").strip()
IMAP_USER = os.getenv("IMAP_USER", "").strip()
IMAP_PASS = os.getenv("IMAP_PASS", "").strip()
IMAP_LABEL = os.getenv("IMAP_LABEL", "INBOX").strip()
SMTP_HOST = os.getenv("SMTP_HOST", "").strip()
SMTP_PORT = int(os.getenv("SMTP_PORT", "465"))
SMTP_USER = os.getenv("SMTP_USER", "").strip()
SMTP_PASS = os.getenv("SMTP_PASS", "").strip()
USE_SMTP_SSL = os.getenv("USE_SMTP_SSL", "true").lower() == "true"
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "").strip()
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini").strip()
DISCIPLINA_FALLBACK = os.getenv("DISCIPLINA", "Não Identificada").strip()
ASSUNTO_PROVA_PALAVRA_CHAVE = os.getenv("ASSUNTO_PROVA_PREFIXO", "Enunciados").strip()
MAX_ISSUES = int(os.getenv("MAX_ISSUES", "15"))
EMAIL_APROVADO_PARA = os.getenv("EMAIL_APROVADO_PARA", "rouget.fundora@gmail.com").strip()
EMAIL_CC_GERAL = os.getenv("EMAIL_CC_GERAL", "rouget.ruano@insutec.ao").strip()
ASSINATURA = os.getenv("ASSINATURA", "Atenciosamente,\nEquipa de Validação").strip()
SCRIPT_DIR = Path(__file__).resolve().parent
SIGN_IMAGE_PATH_FROM_ENV = os.getenv("SIGN_IMAGE_PATH", "./img/assina.png").strip()
SIGN_IMAGE_PATH_ABSOLUTE = SCRIPT_DIR / SIGN_IMAGE_PATH_FROM_ENV
SIGN_IMAGE_PATH = str(SIGN_IMAGE_PATH_ABSOLUTE)
SIGN_IMAGE_WIDTH_CM = float(os.getenv("SIGN_IMAGE_WIDTH_CM", "5"))
REPLY_SENDER_ON_ERROR_FLAG = os.getenv("REPLY_SENDER_ON_ERROR", "true").lower() == "true"


# ===================================================================
# FUNÇÕES DE CARREGAMENTO DE MODELOS (Apenas LanguageTool)
# ===================================================================
def carregar_lang_tool(idioma='pt-PT'):
    """
    Carrega o LanguageTool apenas uma vez no início.
    """
    if language_tool_python is None:
        logging.warning("language_tool_python não instalado. Verificação ortográfica desativada.")
        return None
    try:
        logging.info(f"Carregando LanguageTool para '{idioma}'...")
        tool = language_tool_python.LanguageTool(idioma)
        tool.check("") # Força o arranque do servidor agora
        logging.info("LanguageTool carregado com sucesso.")
        return tool
    except Exception as e:
        logging.error(f"Não foi possível carregar o LanguageTool: {e}")
        return None

LANG_TOOL = carregar_lang_tool('pt-PT') # Carrega o LanguageTool
# ===================================================================


# ==============================
# Funções Utilitárias (Função de extração XPath)
# ==============================
def clean_header(raw_header: Optional[str]) -> str:
    if not raw_header: return ""
    try: return str(make_header(decode_header(raw_header)))
    except Exception: return raw_header

def get_text_from_xml_element(element):
    """
    Função auxiliar robusta para extrair TODO o texto (w:t)
    de dentro de qualquer elemento XML (parágrafo, tabela, content control).
    """
    if element is None:
        return ""
    try:
        # .//w:t encontra todos os nós de texto descendentes
        text_nodes = element.xpath('.//w:t') 
        if text_nodes:
            # Junta todo o texto encontrado.
            return " ".join(node.text for node in text_nodes if node.text)
    except Exception as e:
        logging.warning(f"Erro menor ao extrair nó de texto: {e}")
    return ""

def extract_full_docx_text(bytes_content: bytes) -> str:
    """
    Extrai o texto de parágrafos, tabelas E CONTROLES DE CONTEÚDO,
    MANTENDO A ORDEM DO DOCUMENTO.
    """
    full_text = []
    try:
        with io.BytesIO(bytes_content) as file_stream:
            doc = Document(file_stream)

            # 1. Extrair Cabeçalhos e Rodapés (Iterar blocos em ordem)
            for section in doc.sections:
                for header in (section.header, section.first_page_header, section.even_page_header,
                               section.footer, section.first_page_footer, section.even_page_footer):
                    if header is None: continue
                    full_text.append(get_text_from_xml_element(header._element))

            full_text.append("\n--- CONTEÚDO PRINCIPAL DO DOCUMENTO ---\n")

            # 2. Extrair Corpo Principal (Parágrafos, Tabelas E Content Controls)
            for block in doc.element.body:
                full_text.append(get_text_from_xml_element(block))
                
        # Limpeza Final:
        raw_text = "\n".join(full_text) 
        clean_text = re.sub(r'[\n\r\t]+', ' ', raw_text)
        clean_text = re.sub(r'\s{2,}', ' ', clean_text)
        
        return clean_text.strip()
        
    except Exception as e:
        logging.error(f"Falha CRÍTICA ao extrair texto do DOCX (xpath method): {e}", exc_info=True)
        return ""
# <--- FIM DA FUNÇÃO CORRIGIDA --->

# ==============================
# Funções de Verificação (Função REGEX Corrigida)
# ==============================

def spelling_issues_pt(tool: 'language_tool_python.LanguageTool', text: str, custom_words: List[str]) -> List[Dict]:
    if tool is None: 
        logging.warning("Verificação ortográfica ignorada (LanguageTool não carregado).")
        return []
    try:
        if hasattr(tool, 'disabled_words'): tool.disabled_words.update(custom_words)
        elif hasattr(tool, 'disable_spellchecking_for_words'): tool.disable_spellchecking_for_words(custom_words)
        matches = tool.check(text or "")
        CATEGORIES_TO_IGNORE = {
            'TYPOGRAPHY', 'STYLE', 'WHITESPACE', 'PUNCTUATION', 
            'GRAMMAR', 'COLLOQUIALISMS', 'REDUNDANCY', 'CASING', 'SPELLING'
        }
        issues = []
        for m in matches:
            if m.category in CATEGORIES_TO_IGNORE: continue
            if m.ruleId in ('MORFOLOGIK_RULE_PT_PT', 'UPPERCASE_SPELLING'): continue
            context = (text[max(0, m.offset - 30):m.offset] + f"⟦{text[m.offset:m.offset+m.errorLength]}⟧" + text[m.offset+m.errorLength:m.offset+m.errorLength+30])
            issues.append({
                "message": m.message,
                "context": context,
                "rule": m.ruleId,
                "category": m.category
            })
        return issues
    except Exception as e:
        logging.warning(f"Erro no LanguageTool durante check: {e}."); return []

def check_for_profanity(text: str, word_list: List[str]) -> List[str]:
    # (Sem alterações)
    text_lower = text.lower()
    found_words = set()
    for word in word_list:
        try:
            pattern = r'\b' + re.escape(word.lower()) + r'\b'
            if re.search(pattern, text_lower):
                found_words.add(word)
        except re.error as e:
            logging.warning(f"Erro de regex ao procurar por '{word}': {e}")
    return list(found_words)

def try_extract_discipline_regex(text: str) -> Optional[str]:
    if not text: return None
    header_sample = text[:3000] 
    lookahead_keywords = r"Ano\s*Lec?tivo\s*:|Data\s*:|Curso\s*:|Docente\s*:|Nome\s*:|Prova\s*:|Duração\s*:|Nº\s*:|Turma\s*:|Ano\s*:"
    patterns = [
        r"(?i)(?:Disciplina|Cadeira|Unidade Curricular|Curricular Unit)\s*[:\.]\s*(.+?)\s*(?=" + lookahead_keywords + r")",
        r"(?i)(?:Assunto)\s*[:\.]\s*(.+?)\s*(?=" + lookahead_keywords + r")"
    ]
    for pattern in patterns:
        match = re.search(pattern, header_sample)
        if match:
            clean_name = match.group(1).strip().rstrip(".,;")
            logging.info(f"Disciplina detectada via REGEX (Multi-linha): '{clean_name}'")
            return clean_name
    logging.warning("Regex principal (com lookahead) falhou. A tentar regex de fallback simples...")
    fallback_pattern = r"(?i)(?:Disciplina|Cadeira|Unidade Curricular|Curricular Unit)\s*[:\.]\s*([^\n\r]+)"
    match = re.search(fallback_pattern, header_sample)
    if match:
        possible_name = match.group(1).strip().rstrip(".,;")
        if not re.search(lookahead_keywords, possible_name, re.IGNORECASE):
            logging.info(f"Disciplina detectada via REGEX de FALLBACK: '{possible_name}'")
            return possible_name
    logging.warning("Disciplina NÃO detectada via REGEX.")
    return None

def evaluate_proof_content(text: str, fallback: str) -> Tuple[str, bool, str, str]:
    if not OPENAI_API_KEY:
        return fallback, False, "Validação OpenAI desativada (API_KEY ausente ou inválida no ficheiro .env).", ""
    disciplina_detectada = try_extract_discipline_regex(text)
    client = OpenAI(api_key=OPENAI_API_KEY)
    if disciplina_detectada:
        instrucao_disciplina = f"A disciplina foi identificada no cabeçalho via REGEX como: '{disciplina_detectada}'. CONSIDERE ISTO COMO VERDADE ABSOLUTA."
        tarefa_1_desc = f"1. **Identificar a Disciplina:** O sistema já identificou que a disciplina é '{disciplina_detectada}'. Apenas confirme."
        tarefa_2_desc = f"2. **Análise de Relevância (TAREFA CRÍTICA):** Analise se as perguntas são pertinentes para a disciplina '{disciplina_detectada}'. Se encontrar perguntas de uma matéria COMPLETAMENTE DIFERENTE, 'permissivel' deve ser false."
    else:
        logging.warning("Regex falhou; a pedir à IA para encontrar a disciplina.")
        instrucao_disciplina = "A disciplina não está explícita no formato padrão. Tente deduzi-la pelo cabeçalho."
        tarefa_1_desc = "1. **Identificar a Disciplina:** Leia o texto e encontre o nome EXATO da disciplina no cabeçalho."
        tarefa_2_desc = "2. **Análise de Relevância (TAREFA CRÍTICA):** Analise CADA PERGUNTA. Verifique se as competências testadas são relevantes para a disciplina identificada."
    prompt = f"""
    Aja como um Coordenador de Curso e especialista em pedagogia universitária em Angola.
    Analise o seguinte texto extraído de um documento Word (cabeçalho e corpo principal). O texto foi "achatado" e pode conter ruído de cabeçalhos/rodapés.
    **Contexto Linguístico:** O documento está em Português de Angola.
    {instrucao_disciplina}
    Siga estas tarefas por ordem de PRIORIDADE:
    {tarefa_1_desc}
    {tarefa_2_desc}
    ---
    **Critérios Pedagógicos (Afetam 'sugestoes')**
    ---
    3.  **Clareza e Nível Cognitivo:** Verifique se as perguntas são claras e usam verbos de ação adequados.
    4.  **Gestão de Contexto:** Se houver erros de copiar/colar no cabeçalho (como texto duplicado), mas as perguntas forem tecnicamente válidas para a disciplina, aceite como permissível, mas note o erro nas sugestões.
    5.  **Decisão Final:**
        * "permissivel": `true` (Se a relevância da disciplina estiver correta).
        * "permissivel": `false` (Se a relevância falhar, ou seja, perguntas de outra matéria).
    Responda APENAS em JSON: {{"disciplina_identificada": "O nome EXATO", "permissivel": true|false, "justificativa": "...", "sugestoes": "..."}}
    """
    try:
        resp = client.chat.completions.create(
            model=OPENAI_MODEL,
            messages=[
                {"role": "system", "content": prompt},
                {"role": "user", "content": text} # Envia o texto "achatado" para a IA
            ],
            temperature=0.0,
            response_format={"type": "json_object"}
        )
        data = json.loads(resp.choices[0].message.content or "{}")
        final_disciplina = disciplina_detectada if disciplina_detectada else data.get("disciplina_identificada", fallback)
        if not disciplina_detectada and data.get("disciplina_identificada"):
             logging.info(f"Disciplina detectada pela IA: '{data.get('disciplina_identificada')}'")
        elif not disciplina_detectada and not data.get("disciplina_identificada"):
             logging.warning("IA também não conseguiu identificar a disciplina.")
        return (
            final_disciplina,
            data.get("permissivel", False),
            data.get("justificativa", "Análise automática."),
            data.get("sugestoes", "Nenhuma sugestão.")
        )
    except Exception as e:
        logging.error(f"Falha na API da OpenAI: {e}");
        return fallback, False, "Erro na comunicação com a IA.", ""

# ====================================================================
# Funções de Documento e E-mail (Existentes - Sem alterações)
# ====================================================================

def add_signature_to_doc(doc_bytes: bytes, image_path_str: str, image_width_cm: float) -> Optional[bytes]:
    # (Sem alterações)
    image_path = Path(image_path_str)
    if not image_path.is_file():
        logging.warning(f"Imagem de assinatura NÃO encontrada em {image_path_str}. A enviar documento original.")
        return None
    try:
        doc_stream = io.BytesIO(doc_bytes)
        doc = Document(doc_stream)
        while doc.paragraphs:
            last_paragraph = doc.paragraphs[-1]
            if not last_paragraph.text.strip() and not last_paragraph._p.xpath('.//w:drawing'):
                p_element = last_paragraph._element
                p_element.getparent().remove(p_element)
            else:
                break
        
        section = doc.sections[0]
        footer = section.first_page_footer if section.different_first_page_header_footer else section.footer
        ftr_element = footer._element
        for child in list(ftr_element):
            ftr_element.remove(child)
        paragraph = footer.add_paragraph()
        paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
        fmt = paragraph.paragraph_format
        fmt.space_before = Pt(0); fmt.space_after = Pt(0)
        run = paragraph.add_run()
        run.add_picture(str(image_path), width=Cm(image_width_cm))
        new_doc_stream = io.BytesIO()
        doc.save(new_doc_stream)
        return new_doc_stream.getvalue()
    except Exception as e:
        logging.error(f"Falha ao adicionar assinatura: {e}", exc_info=True)
        return None

def convert_docx_to_pdf_linux(docx_bytes: bytes) -> bytes:
    # (Sem alterações)
    soffice_cmd = shutil.which("libreoffice") or shutil.which("soffice")
    if not soffice_cmd: raise FileNotFoundError("Executável 'libreoffice' ou 'soffice' não encontrado.")
    with tempfile.TemporaryDirectory() as temp_dir:
        temp_dir_path = Path(temp_dir)
        docx_path = temp_dir_path / "input.docx"
        with open(docx_path, "wb") as f: f.write(docx_bytes)
        cmd = [soffice_cmd, "--headless", "--convert-to", "pdf", "--outdir", temp_dir, str(docx_path)]
        try: subprocess.run(cmd, capture_output=True, text=True, timeout=30, check=True)
        except subprocess.CalledProcessError as e: logging.error(f"Falha LibreOffice: {e.stderr}"); raise
        pdf_path = temp_dir_path / "input.pdf"
        if not pdf_path.exists(): raise FileNotFoundError(f"PDF não criado em {pdf_path}")
        with open(pdf_path, "rb") as f: return f.read()

def send_email(to_addr: str, subject: str, body: str, attachments: List[Tuple[str, bytes]] = None, cc_addr: Optional[str] = None, assinatura: str = "", sign_image_path: str = "", sign_image_width_cm: float = 5.0):
    # (Sem alterações)
    if not all([SMTP_HOST, SMTP_USER, SMTP_PASS]): raise ConnectionError("Configurações de SMTP ausentes.")
    msg = EmailMessage()
    msg["From"] = SMTP_USER; msg["To"] = to_addr; msg["Subject"] = subject
    if cc_addr: msg["Cc"] = cc_addr
    assinatura_html = ""
    image_cid_str_with_brackets = None
    if assinatura:
        assinatura_html = f"<br><br><p>{assinatura.replace(chr(10), '<br>')}</p>"
    image_path_obj = Path(sign_image_path)
    if assinatura_html and image_path_obj.is_file():
        try:
            image_cid_str_with_brackets = make_msgid(domain="insutec.ao")
            image_cid_for_html = image_cid_str_with_brackets.strip('<>')
            width_px = int(sign_image_width_cm * 37.795)
            assinatura_html += f'<img src="cid:{image_cid_for_html}" width="{width_px}">'
        except Exception: image_cid_str_with_brackets = None
    msg.set_content(f"{body}\n\n{assinatura}", 'plain', 'utf-8')
    msg.add_alternative(f"<html><body><p>{body.replace(chr(10), '<br>')}</p>{assinatura_html}</body></html>", 'html', 'utf-8')
    if image_cid_str_with_brackets and image_path_obj.is_file():
        try:
            img_data = image_path_obj.read_bytes()
            img_part = MIMEImage(img_data)
            img_part.add_header('Content-ID', image_cid_str_with_brackets)
            img_part.add_header('Content-Disposition', 'inline', filename=image_path_obj.name)
            msg.attach(img_part)
        except Exception: pass
    if attachments:
        for fname, fbytes in attachments:
            try:
                if fname.lower().endswith('.txt'):
                    txt_part = MIMEText(fbytes.decode('utf-8'), 'plain', 'utf-8')
                    txt_part.add_header('Content-Disposition', 'attachment', filename=fname)
                    msg.attach(txt_part)
                else:
                    maintype, subtype = (mimetypes.guess_type(fname)[0] or 'application/octet-stream').split('/')
                    msg.add_attachment(fbytes, maintype=maintype, subtype=subtype, filename=fname)
            except Exception: pass
    try:
        smtp_class = smtplib.SMTP_SSL if USE_SMTP_SSL else smtplib.SMTP
        with smtp_class(SMTP_HOST, SMTP_PORT) as s:
            if not USE_SMTP_SSL: s.starttls()
            s.login(SMTP_USER, SMTP_PASS)
            s.send_message(msg)
    except Exception as e: 
        logging.error(f"Falha no envio do e-mail: {e}")
        raise # Levanta a excepção para que o 'try/except' principal a apanhe

# ====================================================================
# Função principal (COM O BUG DE LOOP CORRIGIDO)
# ====================================================================
def process_inbox():
    if not all([IMAP_HOST, IMAP_USER, IMAP_PASS, EMAIL_APROVADO_PARA, EMAIL_CC_GERAL]):
        logging.error("Configurações de IMAP/E-mail ausentes."); return
    
    imap = None
    try:
        imap = imaplib.IMAP4_SSL(IMAP_HOST)
        imap.login(IMAP_USER, IMAP_PASS)
        imap.select(f'"{IMAP_LABEL}"')
        status, data = imap.search(None, '(UNSEEN)')
        if status != "OK" or not data[0]: 
            logging.info("Nenhum e-mail não lido encontrado.")
            return

        email_ids = data[0].split()
        logging.info(f"Encontrados {len(email_ids)} e-mails não lidos.")

        for num in email_ids:
            try:
                res, raw_full = imap.fetch(num, '(BODY.PEEK[])')
                if res != 'OK': continue
                msg = email.message_from_bytes(raw_full[0][1])
                subject = clean_header(msg.get("Subject", ""))
                if ASSUNTO_PROVA_PALAVRA_CHAVE.lower() not in subject.lower(): 
                    logging.warning(f"E-mail ID {num} ignorado (Assunto não corresponde): '{subject}'")
                    # Ignora, mas NÃO marca como lido. Pode ser outro e-mail.
                    continue 

                logging.info(f"Processando e-mail ID {num}: '{subject}'")
                from_header = clean_header(msg.get("From", ""))
                sender_name, sender_email = parseaddr(from_header)
                if not sender_email: sender_email = from_header

                docx_parts = [(clean_header(p.get_filename()), p.get_payload(decode=True))
                                for p in msg.walk() if p.get_filename() and p.get_filename().lower().endswith(".docx")]

                if not docx_parts:
                    logging.warning(f"E-mail ID {num} tem assunto válido mas não tem anexo .docx. A marcar como lido.")
                    imap.store(num, '+FLAGS', '\\Seen'); continue

                # <--- AQUI ESTÁ A CORREÇÃO CRÍTICA --->
                # Itera sobre CADA anexo .docx, em vez de apenas o primeiro.
                for fname, file_bytes in docx_parts:
                    
                    # Ignora ficheiros temporários do Word
                    if fname.startswith('~$'):
                        logging.warning(f"Ignorando ficheiro temporário: {fname} (do E-mail ID {num})")
                        continue # Pula para o próximo anexo

                    logging.info(f"A processar anexo: {fname} (do E-mail ID {num})")
                    text = extract_full_docx_text(file_bytes)
                    if not text: 
                        logging.warning(f"Ficheiro {fname} (ID {num}) parece estar vazio ou corrompido. A ignorar este ficheiro.")
                        continue # Pula para o próximo anexo

                    # 1. Verificação de Profanidade
                    found_profanities = []
                    is_permissible_profanidade = True

                    # 2. Verificação Ortográfica
                    issues = spelling_issues_pt(LANG_TOOL, text, PALAVRAS_PERSONALIZADAS)
                    is_permissible_ortografia = len(issues) <= MAX_ISSUES

                    # 3. Verificação de Conteúdo (IA)
                    disciplina, is_permissible_ia, justification, suggestions = evaluate_proof_content(text, DISCIPLINA_FALLBACK)

                    # Decisão Final
                    final_is_permissible = (
                        is_permissible_ia and 
                        is_permissible_ortografia and 
                        is_permissible_profanidade
                    )

                    # Criar detalhes dos erros
                    detalhes_ortografia_str = ""
                    if issues:
                        detalhes_ortografia_str = "\n\n--- Detalhes dos Erros Ortográficos Relevantes ---\n"
                        for i, erro in enumerate(issues, 1):
                            msg = erro.get('message', 'Erro desconhecido')
                            ctx = erro.get('context', 'Sem contexto')
                            detalhes_ortografia_str += f"{i}. {msg}\n    Contexto: ...{ctx.strip()}...\n"
                    
                    # Atualizar 'report_content'
                    report_content = (
                        f"RELATÓRIO DE VALIDAÇÃO AUTOMÁTICA\n"
                        f"====================================\n"
                        f"- Remetente: {from_header}\n"
                        f"- Ficheiro: {fname}\n"
                        f"- Disciplina: {disciplina}\n"
                        f"---\n"
                        f"STATUS: {'ACEITÁVEL' if final_is_permissible else 'REQUER REVISÃO'}\n"
                        f"- Profanidade: Nenhuma detetada (Verificação Desativada)\n"
                        f"- Ortografia: {len(issues)} erros relevantes (Max: {MAX_ISSUES})\n"
                        f"- IA/Conteúdo: {'Aprovado' if is_permissible_ia else 'Rejeitado'}\n"
                        f"- Justificativa IA: {justification}\n"
                        f"- Sugestões: {suggestions}\n"
                        f"{detalhes_ortografia_str}"
                    )

                    # Lógica de envio
                    report_attachment = ("relatorio_validacao.txt", report_content.encode('utf-8'))
                    original_doc_attachment = (fname, file_bytes)
                    common_email_args = {"assinatura": ASSINATURA, "sign_image_path": SIGN_IMAGE_PATH, "sign_image_width_cm": SIGN_IMAGE_WIDTH_CM}

                    logging.info(f"A enviar notificações para o ficheiro {fname} (Status: {'ACEITÁVEL' if final_is_permissible else 'REQUER REVISÃO'})...")

                    if final_is_permissible:
                        modified_doc_bytes = add_signature_to_doc(file_bytes, SIGN_IMAGE_PATH, SIGN_IMAGE_WIDTH_CM)
                        if not modified_doc_bytes:
                            logging.warning(f"Falha ao assinar {fname}. A enviar e-mail de erro.")
                            send_email(sender_email, f"[ERRO SISTEMA] Enunciado: {disciplina}", "Aprovado, mas falha técnica ao inserir assinatura.", [report_attachment], EMAIL_CC_GERAL, **common_email_args)
                            continue # Continua para o próximo anexo
                        
                        try:
                            pdf_bytes = convert_docx_to_pdf_linux(modified_doc_bytes)
                            doc_final = (f"ASSINADO_{Path(fname).with_suffix('.pdf').name}", pdf_bytes)
                        except Exception as e:
                            logging.warning(f"Falha na conversão para PDF ({fname}): {e}. A enviar DOCX assinado.")
                            doc_final = (f"ASSINADO_{fname}", modified_doc_bytes)
                        
                        send_email(EMAIL_APROVADO_PARA, f"[APROVADO] Enunciado: {disciplina}", f"Enunciado de {sender_name} ({fname}) aprovado e assinado.", [doc_final], EMAIL_CC_GERAL, **common_email_args)
                        send_email(sender_email, f"[APROVADO] O seu enunciado: {disciplina} ({fname})", "O seu enunciado foi aprovado e encaminhado.", [report_attachment, original_doc_attachment], EMAIL_CC_GERAL, **common_email_args)
                    else:
                        send_email(sender_email, f"[REVISÃO NECESSÁRIA] Enunciado: {disciplina} ({fname})", "O enunciado requer revisão. Ver relatório anexo.", [report_attachment, original_doc_attachment], EMAIL_CC_GERAL, **common_email_args)

                # <--- FIM DO LOOP DE ANEXOS --->
                
                # Marca o e-mail como lido DEPOIS de todos os anexos terem sido processados
                logging.info(f"Processamento do ID {num} concluído. A marcar como lido.")
                imap.store(num, '+FLAGS', '\\Seen')

            except Exception as e:
                logging.error(f"Erro fatal ao processar e-mail ID {num}: {e}", exc_info=True)
                try: 
                    # Tenta marcar como lido mesmo em caso de erro para não bloquear o loop
                    imap.store(num, '+FLAGS', '\\Seen')
                except: pass
    finally:
        if imap:
            try: imap.close(); imap.logout()
            except: pass

def main():
    try: process_inbox()
    except Exception as e: logging.critical(f"Erro fatal: {e}", exc_info=True); sys.exit(1)

if __name__ == "__main__":
    # Verificação de dependências no arranque
    if language_tool_python is None:
        logging.error("Dependência 'language_tool_python' não encontrada. A verificação ortográfica será desativada.")
        logging.error("Instale com: pip install language-tool-python")
    if OpenAI is None:
        logging.error("Dependência 'openai' não encontrada. A verificação de conteúdo IA será desativada.")
        logging.error("Instale com: pip install openai")
    
    if LANG_TOOL is None and language_tool_python is not None:
         logging.error("Não foi possível carregar o LanguageTool. Verificação ortográfica desativada.")

    logging.info("Serviço iniciado.")
    main()
    logging.info("Serviço finalizado.")
