import os
import re
import sys
import json
import time
import email
import imaplib
import smtplib
import tempfile
import logging
import mimetypes 
import io 
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'
]
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:
            # Modificado para incluir rodapés na extração de texto
            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 []

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)
    prompt = f"""
    Aja como um Coordenador de Curso. Analise o seguinte texto extraído de um documento Word, que contém primeiro o texto do cabeçalho, seguido do corpo principal.

    Siga estas tarefas por ordem de PRIORIDADE:
    1.  **Identificar a Disciplina (TAREFA CRÍTICA):** Leia o texto e encontre o nome EXATO da disciplina. Dê prioridade absoluta ao texto que aparece antes de '--- CONTEÚDO PRINCIPAL DO DOCUMENTO ---'. Procure por etiquetas como "Disciplina:", "Unidade Curricular:", "Curso:". NÃO deduza ou infira a disciplina a partir do conteúdo das perguntas se um nome explícito estiver presente.
    2.  **Análise de Conteúdo:** Usando o nome da disciplina que identificou, avalie se o conteúdo é relevante e adequado.
    3.  **Decisão e Sugestões:** Decida se o enunciado é "Permissível" ou "Requer Revisão" e forneça sugestões.

    Responda APENAS em JSON: {{"disciplina_identificada": "O nome EXATO encontrado", "permissivel": true|false, "justificativa": "análise", "sugestoes": "sugestões"}}
    """
    try:
        resp = client.chat.completions.create(model=OPENAI_MODEL, messages=[{"role": "user", "content": prompt}, {"role": "assistant", "content": text}], temperature=0.1, 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.", ""


# ====================================================================
# CORREÇÃO: Mover a lógica para o RODAPÉ (Footer)
# ====================================================================
def add_signature_to_doc(doc_bytes: bytes, image_path_str: str, image_width_cm: float) -> Optional[bytes]:
    """
    Limpa completamente o rodapé (priorizando o da primeira página)
    e insere apenas a imagem de assinatura.
    """
    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 # Alterado de 'header' para 'footer'

        # Verificar se o documento usa rodapés de primeira página diferentes
        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
        
        # --- INÍCIO DA LÓGICA DE LIMPEZA "NUCLEAR" (no Rodapé) ---
        
        ftr_element = footer._element
        logging.info("A executar limpeza 'nuclear' v7 (a remover *todos* os elementos filhos do rodapé)...")

        # Iterar sobre uma cópia (list()) e remover do elemento original
        for child in list(ftr_element):
            ftr_element.remove(child)

        logging.info("Rodapé limpo com sucesso: Todos os elementos filhos foram removidos.")
        
        # 2. Agora o rodapé está ABSOLUTAMENTE vazio. Adicionar a assinatura.
        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.")
        
        # --- FIM DA LÓGICA DE LIMPEZA ---

        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


# ====================================================================
# 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

    # --- Lógica de Assinatura e HTML ---
    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:
                    msg.add_attachment(
                        fbytes,
                        maintype='application',
                        subtype='octet-stream',
                        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

# ====================================================================
# RESTO DO CÓDIGO (sem alterações)
# ====================================================================

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

                    issues = spelling_issues_pt(text, PALAVRAS_PERSONALIZADAS)
                    disciplina, is_permissible_ia, justification, suggestions = evaluate_proof_content(text, DISCIPLINA_FALLBACK)
                    
                    is_permissible_ortografia = len(issues) <= MAX_ISSUES
                    final_is_permissible = is_permissible_ia and is_permissible_ortografia

                    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 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'}"
                    )
                    
                    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...")
                        modified_doc_bytes = add_signature_to_doc(file_bytes, SIGN_IMAGE_PATH, SIGN_IMAGE_WIDTH_CM)
                        
                        if modified_doc_bytes:
                            doc_attachment = (f"ASSINADO_{fname}", modified_doc_bytes)
                            logging.info(f"Assinatura adicionada. A enviar como 'ASSINADO_{fname}'.")
                        else:
                            doc_attachment = (fname, file_bytes)
                            logging.warning(f"A enviar documento original sem assinatura.")

                        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 enunciado original (assinado, se configurado) 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 na ortografia (limite de {MAX_ISSUES} problemas excedido) e/ou no conteúdo, conforme decisão da IA.\n\n"
                            f"Por 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.")
