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 # NOVO IMPORT
import shutil # NOVO IMPORT
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 

# REMOVIDO: 'docx2pdf' não é mais usado

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:
            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.", ""


# ====================================================================
# Lógica do RODAPÉ (estável)
# ====================================================================
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 

        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' (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.")
        
        # --- 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

# ====================================================================
# NOVA FUNÇÃO: Conversão para PDF via LibreOffice (Linux)
# ====================================================================
def convert_docx_to_pdf_linux(docx_bytes: bytes) -> bytes:
    """
    Converte bytes de um DOCX para bytes de um PDF usando o LibreOffice.
    """
    # 1. Encontrar o executável do LibreOffice
    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).")

    # 2. Usar um diretório temporário
    with tempfile.TemporaryDirectory() as temp_dir:
        temp_dir_path = Path(temp_dir)
        docx_path = temp_dir_path / "input.docx"
        
        # 3. Escrever os bytes do docx num ficheiro temporário
        with open(docx_path, "wb") as f:
            f.write(docx_bytes)
            
        # 4. Construir e executar o comando de conversão
        cmd = [
            soffice_cmd,
            "--headless", # Não abrir GUI
            "--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

        # 5. Encontrar o ficheiro PDF de saída
        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}")
            
        # 6. Ler os bytes do PDF
        with open(pdf_path, "rb") as f:
            pdf_bytes = f.read()
            
        # 7. O diretório temporário (e os ficheiros .docx/.pdf) é apagado automaticamente
        
        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 (Modificada para usar a nova função de PDF)
# ====================================================================

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 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:
                                # 1. Chamar a nova função de conversão
                                pdf_bytes = convert_docx_to_pdf_linux(modified_doc_bytes)
                                logging.info("Conversão para PDF bem-sucedida.")
                                
                                # 2. Definir o anexo final (PDF)
                                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.")
                                # Fallback: enviar o .docx assinado
                                doc_attachment = (f"ASSINADO_{fname}", modified_doc_bytes)
                        
                        else: # Falha ao assinar
                            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 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.")
