import os
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
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.text import WD_TAB_ALIGNMENT

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

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'
]

### ALTERAÇÃO 1: ADICIONADA LISTA DE PROFANIDADES ###
# Adicione aqui todas as palavras que devem causar rejeição automática.
# Serão verificadas sem distinção de maiúsculas/minúsculas.
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()
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()
SIGN_IMAGE_PATH = os.getenv("SIGN_IMAGE_PATH", "./img/assina.png").strip()
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 Utilitárias
# ==============================
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 extract_full_docx_text(bytes_content: bytes) -> str:
    full_text = []
    with io.BytesIO(bytes_content) as file_stream:
        doc = Document(file_stream)
        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
                for table in header.tables:
                    for row in table.rows:
                        for cell in row.cells:
                            full_text.append(cell.text)
                for paragraph in header.paragraphs:
                    full_text.append(paragraph.text)

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

        for paragraph in doc.paragraphs:
            full_text.append(paragraph.text)
        for table in doc.tables:
            for row in table.rows:
                for cell in row.cells:
                    full_text.append(cell.text)

    return "\n".join(full_text).strip()

def spelling_issues_pt(text: str, custom_words: List[str]) -> List[Dict]:
    if language_tool_python is None: return []
    try:
        tool = language_tool_python.LanguageTool('pt-BR')
        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 "")
        issues = []
        for m in matches:
            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})
        return issues
    except Exception as e:
        logging.warning(f"LanguageTool indisponível: {e}."); return []


### ALTERAÇÃO 2: NOVA FUNÇÃO DE FILTRO DE PROFANIDADE ###
def check_for_profanity(text: str, word_list: List[str]) -> List[str]:
    """
    Verifica o texto contra uma lista de palavras impróprias.
    Usa limites de palavra (\b) para evitar falsos positivos (ex: 'merda' em 'comerciante').
    Retorna a lista de palavras encontradas.
    """
    text_lower = text.lower()
    found_words = set()
    for word in word_list:
        try:
            # \b = word boundary. Garante que estamos a apanhar a palavra inteira.
            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)
# ========================================================


# ====================================================================
# FUNÇÃO MODIFICADA (evaluate_proof_content)
# ====================================================================
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).", ""
    client = OpenAI(api_key=OPENAI_API_KEY)

    ### ALTERAÇÃO 3: PROMPT DA IA ATUALIZADO (Versão 4) ###
    # Adicionada uma tarefa CRÍTICA (TAREFA 2) para a IA também
    # procurar por linguagem imprópria, como dupla verificação.
    prompt = f"""
    Aja como um Coordenador de Curso. Analise o seguinte texto extraído de um documento Word (cabeçalho e corpo principal).

    Siga estas tarefas por ordem de PRIORIDADE:

    1.  **Identificar a Disciplina (TAREFA 1):** Leia o texto e encontre o nome EXATO da disciplina (ex: "Base de Dados I"). Dê prioridade ao cabeçalho.

    2.  **Análise de Profanidade (TAREFA 2 - CRÍTICA):** Verifique se o texto contém linguagem grosseira, imprópria, ofensiva ou profanidade (ex: "merda", "porra", "isto é uma estupidez"). Se encontrar QUALQUER linguagem imprópria, o documento INTEIRO deve ser considerado "Requer Revisão" (permissivel: false) e a justificativa deve citar isso explicitamente.

    3.  **Análise de Relevância (TAREFA 3):** Usando o nome da disciplina (TAREFA 1), analise CADA PERGUNTA no 'CONTEÚDO PRINCIPAL'. Verifique se TODAS as competências testadas (ex: Modelar ER, escrever SQL, definir conceitos) são relevantes para a disciplina.

    4.  **Regra de Falha Explícita (TAREFA 4 - CRÍTICA):** Se encontrar UMA ÚNICA pergunta que teste uma competência de uma matéria COMPLETAMENTE DIFERENTE (ex: perguntar o 'Teorema de Pitágoras' numa prova de 'Base de Dados'), o documento INTEIRO deve ser considerado "Requer Revisão".

    5.  **Gestão de Contexto Confuso (TAREFA 5 - IMPORTANTE):**
        O enunciado pode conter erros de copiar/colar ou contradições (ex: definir um cenário 'Companhia de Seguros' e depois mudar subitamente para 'Biblioteca', mas continuar a pedir tarefas sobre o cenário 'Companhia de Seguros').

        **NÃO REJEITE** o documento se a confusão for apenas no texto de contexto, mas as *TAREFAS* pedidas (ex: 'Escreva o script SQL para a entidade acidentes', 'Crie o modelo ER') forem pertinentes para a disciplina identificada.

        Se as *competências* (SQL, Modelação) são relevantes, considere "Permissível", mas aponte a confusão (ex: a troca de 'seguros' por 'biblioteca') no campo 'sugestoes'.

    6.  **Decisão e Sugestões (TAREFA 6):**
        - "permissivel": true (APENAS se TODAS as competências testadas forem relevantes E a TAREFA 2 não falhar, mesmo que o texto de contexto (TAREFA 5) seja confuso).
        - "permissivel": false (Se a TAREFA 2, 3 ou 4 falharem).
        - "justificativa": A sua análise e a razão da decisão. Se a TAREFA 2 falhar, mencione a linguagem imprópria.
        - "sugestoes": Se for "Permissível" mas confuso (TAREFA 5), aponte aqui o erro de contexto.

    Responda APENAS em JSON: {{"disciplina_identificada": "O nome EXATO encontrado", "permissivel": true|false, "justificativa": "A sua análise e a razão da decisão.", "sugestoes": "Sugestões de melhoria, apontando a confusão se houver."}}
    """
    # === FIM DO PROMPT MODIFICADO ===

    try:
        resp = client.chat.completions.create(
            model=OPENAI_MODEL,
            messages=[
                {"role": "system", "content": prompt},
                {"role": "user", "content": text}
            ],
            temperature=0.0,
            response_format={"type": "json_object"}
        )
        data = json.loads(resp.choices[0].message.content or "{}")
        return (
            data.get("disciplina_identificada", fallback),
            data.get("permissivel", False),
            data.get("justificativa", "N/A"),
            data.get("sugestoes", "N/A")
        )
    except Exception as e:
        logging.error(f"Falha na API da OpenAI: {e}");
        return fallback, False, "Erro na comunicação com a IA.", ""


# ====================================================================
# Lógica do RODAPÉ (estável)
# ====================================================================
def add_signature_to_doc(doc_bytes: bytes, image_path_str: str, image_width_cm: float) -> Optional[bytes]:
    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

    logging.info(f"Imagem de assinatura encontrada. A tentar limpar o RODAPÉ e inserir...")

    try:
        doc_stream = io.BytesIO(doc_bytes)
        doc = Document(doc_stream)

        section = doc.sections[0]
        footer = None

        if section.different_first_page_header_footer:
            logging.info("Documento usa 'Rodapé de Primeira Página Diferente'. A modificar esse.")
            footer = section.first_page_footer
        else:
            logging.info("Documento usa rodapé principal. A modificar esse.")
            footer = section.footer

        ftr_element = footer._element
        logging.info("A executar limpeza 'nuclear' (a remover *todos* os elementos filhos do rodapé)...")

        for child in list(ftr_element):
            ftr_element.remove(child)

        logging.info("Rodapé limpo com sucesso: Todos os elementos filhos foram removidos.")

        paragraph = footer.add_paragraph()
        paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
        run = paragraph.add_run()
        run.add_picture(str(image_path), width=Cm(image_width_cm))

        logging.info("Assinatura inserida no rodapé limpo.")

        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 ao documento Word (no rodapé): {e}", exc_info=True)
        return None

# ====================================================================
# Conversão para PDF via LibreOffice (Linux)
# ====================================================================
def convert_docx_to_pdf_linux(docx_bytes: bytes) -> bytes:
    soffice_cmd = shutil.which("libreoffice") or shutil.which("soffice")
    if not soffice_cmd:
        raise FileNotFoundError("Executável 'libreoffice' ou 'soffice' não encontrado. Por favor, instale-o (sudo apt-get install libreoffice).")

    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)
        ]

        logging.info(f"A executar comando: {' '.join(cmd)}")
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, check=True)
            logging.info(f"LibreOffice stdout: {result.stdout}")
            if result.stderr:
                logging.warning(f"LibreOffice stderr: {result.stderr}")
        except subprocess.CalledProcessError as e:
            logging.error(f"Falha na conversão do LibreOffice. Erro: {e.stderr}")
            raise
        except subprocess.TimeoutExpired:
            logging.error("Conversão do LibreOffice demorou demasiado (timeout).")
            raise

        pdf_path = temp_dir_path / "input.pdf"
        if not pdf_path.exists():
            raise FileNotFoundError(f"LibreOffice não criou o ficheiro PDF esperado em {pdf_path}")

        with open(pdf_path, "rb") as f:
            pdf_bytes = f.read()

        return pdf_bytes

# ====================================================================
# Função de E-mail (estável)
# ====================================================================
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
):
    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_formatada = assinatura.replace('\n', '<br>')
        assinatura_html = f"<br><br><p>{assinatura_formatada}</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 as e:
            logging.warning(f"Erro ao preparar imagem de assinatura: {e}")
            image_cid_str_with_brackets = None

    plain_body = f"{body}\n\n{assinatura}"
    msg.set_content(plain_body, 'plain', 'utf-8')

    body_formatado = body.replace('\n', '<br>')
    html_body = f"""
    <html>
    <body>
        <p>{body_formatado}</p>
        {assinatura_html}
    </body>
    </html>
    """
    msg.add_alternative(html_body, '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 as e:
            logging.warning(f"Falha ao anexar imagem de assinatura {sign_image_path}: {e}")

    # Anexos (Relatório e Documento)
    if attachments:
        for fname, fbytes in attachments:
            try:
                if fname.lower().endswith('.txt'):
                    report_string = fbytes.decode('utf-8')
                    txt_part = MIMEText(report_string, '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 as e:
                logging.error(f"Falha ao anexar ficheiro {fname}: {e}")

    # Envio do E-mail
    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 ao enviar e-mail para {to_addr} (CC: {cc_addr}): {e}"); raise

# ====================================================================
# Função principal
# ====================================================================

def process_inbox():
    if not all([IMAP_HOST, IMAP_USER, IMAP_PASS, EMAIL_APROVADO_PARA, EMAIL_CC_GERAL]):
        logging.error("Configurações de IMAP ou E-mail (EMAIL_APROVADO_PARA / EMAIL_CC_GERAL) 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 para verificar.")

        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.info(f"Ignorando e-mail (assunto '{subject}' não é de enunciado). Permanece não lido.")
                    continue

                logging.info(f"E-mail de enunciado encontrado: '{subject}'. A processar...")

                from_header = clean_header(msg.get("From", ""))
                sender_name, sender_email = parseaddr(from_header)
                if not sender_email:
                    sender_email = from_header
                    sender_name = "Remetente Desconhecido"

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

                if not docx_parts:
                    logging.warning(f"E-mail de enunciado de '{from_header}' não continha anexo .docx.")
                    continue

                for fname, file_bytes in docx_parts:
                    text = extract_full_docx_text(file_bytes)
                    if not text: continue
                    
                    ### ALTERAÇÃO 4: LÓGICA DE VALIDAÇÃO PRINCIPAL ATUALIZADA ###
                    
                    # 1. Verificação de Profanidade (Rígida)
                    found_profanities = check_for_profanity(text, LISTA_PALAVRAS_INAPROPRIADAS)
                    is_permissible_profanidade = len(found_profanities) == 0

                    # 2. Verificação Ortográfica
                    issues = spelling_issues_pt(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)

                    # 4. Decisão Final (AGORA INCLUI A VERIFICAÇÃO DE PROFANIDADE)
                    final_is_permissible = is_permissible_ia and is_permissible_ortografia and is_permissible_profanidade

                    # 5. Relatório (AGORA INCLUI A NOVA SECÇÃO)
                    report_content = (
                        f"RELATÓRIO DE VALIDAÇÃO AUTOMÁTICA\n"
                        f"====================================\n"
                        f"- Remetente: {from_header}\n"
                        f"- Ficheiro: {fname}\n"
                        f"- Disciplina Identificada (IA): {disciplina}\n"
                        f"---\n"
                        f"ANÁLISE DE LINGUAGEM IMPRÓPRIA (FILTRO)\n"
                        f"- Palavras Encontradas: {', '.join(found_profanities) if not is_permissible_profanidade else 'Nenhuma'}\n"
                        f"- Status Linguagem: {'Aceitável' if is_permissible_profanidade else 'REJEITADO (Linguagem Imprópria)'}\n"
                        f"---\n"
                        f"ANÁLISE ORTOGRÁFICA\n"
                        f"- Problemas Encontrados: {len(issues)} (Tolerância Máxima: {MAX_ISSUES})\n"
                        f"- Status Ortografia: {'Aceitável' if is_permissible_ortografia else 'Requer Atenção'}\n"
                        f"---\n"
                        f"ANÁLISE DE CONTEÚDO (IA)\n"
                        f"- Decisão IA: {'Permissível' if is_permissible_ia else 'Requer Revisão'}\n"
                        f"- Justificativa: {justification}\n"
                        f"- Sugestões: {suggestions}\n"
                        f"---\n"
                        f"DECISÃO FINAL: {'PERMISSÍVEL' if final_is_permissible else 'REQUER REVISÃO'}"
                    )
                    ### FIM DA ALTERAÇÃO 4 ###

                    report_attachment = ("relatorio_validacao.txt", report_content.encode('utf-8'))

                    common_email_args = {
                        "assinatura": ASSINATURA,
                        "sign_image_path": SIGN_IMAGE_PATH,
                        "sign_image_width_cm": SIGN_IMAGE_WIDTH_CM
                    }

                    if final_is_permissible:
                        logging.info(f"Documento '{fname}' permissível. A tentar adicionar assinatura ao rodapé...")
                        modified_doc_bytes = add_signature_to_doc(file_bytes, SIGN_IMAGE_PATH, SIGN_IMAGE_WIDTH_CM)

                        doc_attachment = None

                        if modified_doc_bytes:
                            logging.info("Assinatura adicionada. A tentar converter para PDF via LibreOffice...")

                            try:
                                pdf_bytes = convert_docx_to_pdf_linux(modified_doc_bytes)
                                logging.info("Conversão para PDF bem-sucedida.")
                                pdf_fname = Path(fname).with_suffix('.pdf').name
                                doc_attachment = (f"ASSINADO_{pdf_fname}", pdf_bytes)

                            except Exception as e:
                                logging.error(f"Falha ao converter DOCX para PDF com LibreOffice: {e}. A enviar o DOCX assinado como fallback.")
                                doc_attachment = (f"ASSINADO_{fname}", modified_doc_bytes)

                        else:
                            doc_attachment = (fname, file_bytes)
                            logging.warning("Falha ao adicionar assinatura. A enviar documento original sem conversão.")

                        attachments = [doc_attachment, report_attachment]

                        logging.info(f"Análise PERMISSÍVEL. A enviar para {EMAIL_APROVADO_PARA} (CC: {EMAIL_CC_GERAL}).")
                        subject_out = f"[PERMISSÍVEL] Enunciado: {disciplina}"
                        body_out = (
                            f"Prezado(a) Coordenador(a),\n\n"
                            f"Um novo enunciado de prova foi APROVADO pelo sistema.\n"
                            f"- Remetente Original: {from_header}\n"
                            f"- Disciplina: {disciplina}\n\n"
                            f"O ficheiro final (assinado e convertido para PDF) e o relatório detalhado estão em anexo."
                        )
                        send_email(
                            to_addr=EMAIL_APROVADO_PARA,
                            cc_addr=EMAIL_CC_GERAL,
                            subject=subject_out,
                            body=body_out,
                            attachments=attachments,
                            **common_email_args
                        )

                    elif REPLY_SENDER_ON_ERROR_FLAG:
                        attachments = [(fname, file_bytes), report_attachment]

                        logging.info(f"Análise REQUER REVISÃO. A devolver ao remetente {sender_email} (CC: {EMAIL_CC_GERAL}).")
                        subject_out = f"[REVISÃO NECESSÁRIA] Enunciado: {disciplina}"
                        body_out = (
                            f"Prezado(a) Professor(a) ({sender_name or sender_email}),\n\n"
                            f"O enunciado de prova que submeteu para a disciplina '{disciplina}' foi analisado e REQUER REVISÃO.\n\n"
                            f"O sistema identificou problemas: "
                            f"{'Linguagem imprópria detectada.' if not is_permissible_profanidade else ''}"
                            f"{'Ortografia (limite de ' + str(MAX_ISSUES) + ' problemas excedido).' if not is_permissible_ortografia else ''}"
                            f"{'Conteúdo (conforme decisão da IA).' if not is_permissible_ia else ''}"
                            f"\n\nPor favor, consulte o relatório em anexo para detalhes.\n\n"
                            f"Após corrigir, por favor, submeta novamente."
                        )
                        send_email(
                            to_addr=sender_email,
                            cc_addr=EMAIL_CC_GERAL,
                            subject=subject_out,
                            body=body_out,
                            attachments=attachments,
                            **common_email_args
                        )
                    else:
                        logging.warning(f"Análise REQUER REVISÃO, mas REPLY_SENDER_ON_ERROR está 'false'. Nenhuma devolução ao remetente será feita.")

                imap.store(num, '+FLAGS', '\\Seen')
                logging.info(f"E-mail original de '{from_header}' foi processado e marcado como lido.")

            except Exception as e:
                logging.error(f"Erro ao processar e-mail ID {num.decode()}: {e}", exc_info=True)
    finally:
        if imap:
            try: imap.close(); imap.logout()
            except Exception: pass

def main():
    try:
        process_inbox()
    except Exception as e:
        logging.critical(f"Erro fatal na execução principal: {e}", exc_info=True)
        sys.exit(1)

if __name__ == "__main__":
    logging.info("Iniciando serviço de validação de enunciados.")
    main()
    logging.info("Serviço de validação finalizado.")
