# ------- FILE: main.py -------
"""
Robô SAPF - main.py
Automação Selenium para preenchimento do formulário de Apoiamento
Fluxo:
 - Abre o Chrome e espera o operador: login + CAPTCHA + clicar em 'Cadastrar Apoiamento'
 - Quando a URL conter o trecho definido, assume a tela de cadastro e inicia o loop
 - Consulta PostgreSQL por registros PENDENTES
 - Baixa PDF via FTP (arquivo: <codigo>.pdf) para PASTA_FICHAS
 - Preenche campos do formulário (IDs definidos no config)
 - Clica no botão 'cadastrar' (ID: cadastrar)
 - Marca registro como ENVIADO no banco
 - Continua em loop até o operador clicar no botão LIMPAR (o que faz o botão 'cadastrar' sumir)
 - Quando detectar que o operador clicou LIMPAR, exibe janela Tkinter "FIM DO PROCESSO"
 - Aguarda o operador clicar SAIR e encerra Selenium e o programa

Gerado para compilar com PyInstaller (--onefile --noconsole)
"""

import os
import sys
import time
import logging
from ftplib import FTP
import psycopg2
import tkinter as tk

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException

# webdriver-manager for automatic chromedriver handling
from webdriver_manager.chrome import ChromeDriverManager

# Load config
try:
    import config
except Exception:
    # If executed as single-file EXE, config can be bundled; expect config.py next to exe
    raise

# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

# Support for PyInstaller temp path
if getattr(sys, 'frozen', False):
    BASE_DIR = sys._MEIPASS
else:
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))

PASTA_FICHAS = config.PASTA_FICHAS
os.makedirs(PASTA_FICHAS, exist_ok=True)

# ------------------------------------------------------------------
# Helper: connect to Postgres and fetch one pending record
# Expecting columns: id, codigo, nome_apoiador, titulo_apoiador, data_apoio (date), analfabeto, nome_agente, titulo_agente
# ------------------------------------------------------------------

def buscar_registro():
    try:
        conn = psycopg2.connect(
            host=config.DB_HOST,
            database=config.DB_NAME,
            user=config.DB_USER,
            password=config.DB_PASS
        )
        cur = conn.cursor()

        cur.execute("""
            SELECT id, codigo, nome_apoiador, titulo_apoiador,
                   data_apoio, analfabeto, nome_agente, titulo_agente
            FROM documentos
            WHERE status = 'PENDENTE'
            ORDER BY id
            LIMIT 1
        """)

        reg = cur.fetchone()
        cur.close()
        conn.close()
        return reg

    except Exception as e:
        logger.error("Erro ao acessar o banco: %s", e)
        return None

# ------------------------------------------------------------------
# Download via FTP: assume arquivo named <codigo>.pdf at FTP root
# ------------------------------------------------------------------

def baixar_pdf_ftp(codigo):
    arquivo = f"{codigo}.pdf"
    destino = os.path.join(PASTA_FICHAS, arquivo)
    try:
        ftp = FTP(config.FTP_HOST, timeout=30)
        ftp.login(config.FTP_USER, config.FTP_PASS)
        with open(destino, 'wb') as f:
            ftp.retrbinary(f"RETR {arquivo}", f.write)
        ftp.quit()
        logger.info("Arquivo baixado: %s", destino)
        return destino
    except Exception as e:
        logger.error("Erro no FTP ao baixar %s: %s", arquivo, e)
        if os.path.exists(destino):
            try:
                os.remove(destino)
            except:
                pass
        return None

# ------------------------------------------------------------------
# Preenche formulário usando os IDs informados em config
# ------------------------------------------------------------------

def preencher_formulario(driver, registro, caminho_pdf):
    (id_doc, codigo, nome, titulo, data_apoio, analfabeto, nome_agente, titulo_agente) = registro

    logger.info("Preenchendo formulário para codigo=%s", codigo)

    # tipoApoiamento:1 (clicar opção)
    try:
        # some implementations may have colon in id; use find_element_by_id
        driver.find_element(By.ID, config.ID_TIPO_APOIAMENTO).click()
    except Exception as e:
        logger.warning("Não conseguiu clicar tipoApoiamento: %s", e)

    # UF - combo de foco
    try:
        uf_elem = driver.find_element(By.ID, config.ID_UF)
        uf_elem.clear()
        uf_elem.send_keys(config.DEFAULT_UF if not registro[2] else registro[2][:2])
    except Exception as e:
        logger.warning("Não conseguiu preencher UF: %s", e)

    # Nome do apoiador
    try:
        driver.find_element(By.ID, config.ID_NOME_APOIADOR).send_keys(nome or '')
    except Exception as e:
        logger.warning("Não conseguiu preencher nomeApoiador: %s", e)

    # Título do apoiador
    try:
        driver.find_element(By.ID, config.ID_TITULO_APOIADOR).send_keys(titulo or '')
    except Exception as e:
        logger.warning("Não conseguiu preencher tituloApoiador: %s", e)

    # Data - espera formatar caso seja date
    try:
        data_str = ''
        if hasattr(data_apoio, 'strftime'):
            data_str = data_apoio.strftime("%d/%m/%Y")
        else:
            data_str = str(data_apoio)
        driver.find_element(By.ID, config.ID_DATA_APOIAMENTO).send_keys(data_str)
    except Exception as e:
        logger.warning("Não conseguiu preencher dataApoio: %s", e)

    # Analfabeto - assumir radio with suffix ; try both options
    try:
        if str(alfabeto := registro[5]).upper() == 'S':
            driver.find_element(By.ID, config.ID_ANALFABETO_SIM).click()
        else:
            driver.find_element(By.ID, config.ID_ANALFABETO_NAO).click()
    except Exception as e:
        logger.warning("Não conseguiu preencher analfabeto: %s", e)

    # Nome agente e título agente
    try:
        driver.find_element(By.ID, config.ID_NOME_AGENTE).send_keys(nome_agente or '')
    except Exception as e:
        logger.warning("Não conseguiu preencher nomeAgente: %s", e)

    try:
        driver.find_element(By.ID, config.ID_TITULO_AGENTE).send_keys(titulo_agente or '')
    except Exception as e:
        logger.warning("Não conseguiu preencher tituloAgente: %s", e)

    # Upload
    if caminho_pdf:
        try:
            # clicar no label para abrir input (se necessário)
            try:
                driver.find_element(By.ID, config.ID_FILE_LABEL).click()
                time.sleep(0.5)
            except Exception:
                pass

            input_file = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
            input_file.send_keys(caminho_pdf)
            logger.info("Upload iniciado: %s", caminho_pdf)

            # aguardar um curto período para o upload completar
            time.sleep(config.UPLOAD_WAIT_SECONDS)
        except Exception as e:
            logger.error("Falha ao enviar arquivo via input[type=file]: %s", e)
    else:
        logger.error("Nenhum arquivo disponível para upload para codigo=%s", codigo)

    return id_doc

# ------------------------------------------------------------------
# Marcar registro como enviado
# ------------------------------------------------------------------

def finalizar_registro(id_doc):
    try:
        conn = psycopg2.connect(
            host=config.DB_HOST,
            database=config.DB_NAME,
            user=config.DB_USER,
            password=config.DB_PASS
        )
        cur = conn.cursor()
        cur.execute("UPDATE documentos SET status='ENVIADO' WHERE id = %s", (id_doc,))
        conn.commit()
        cur.close()
        conn.close()
        logger.info("Registro %s marcado como ENVIADO", id_doc)
    except Exception as e:
        logger.error("Erro ao atualizar registro %s: %s", id_doc, e)

# ------------------------------------------------------------------
# Detecta se o operador clicou no LIMPAR (botão 'cadastrar' desapareceu)
# ------------------------------------------------------------------

def operador_clicou_limpar(driver):
    try:
        driver.find_element(By.ID, config.ID_CADASTRAR)
        return False
    except NoSuchElementException:
        return True
    except WebDriverException as e:
        logger.error("Erro webdriver ao checar element: %s", e)
        return False

# ------------------------------------------------------------------
# Janela final Tkinter
# ------------------------------------------------------------------

def mostrar_fim_de_processo():
    fim = tk.Tk()
    fim.title("FIM DO PROCESSO")
    fim.geometry("360x160")

    tk.Label(fim, text="Processo encerrado pelo operador.", font=("Arial", 12)).pack(pady=18)
    tk.Label(fim, text="Clique em SAIR para encerrar.").pack()

    btn = tk.Button(fim, text="SAIR", font=("Arial", 12), width=12, command=fim.destroy)
    btn.pack(pady=12)

    # loop bloqueante até o usuário clicar SAIR
    fim.mainloop()

# ------------------------------------------------------------------
# Fluxo principal
# ------------------------------------------------------------------

def main():
    # inicializa Chrome via webdriver-manager
    try:
        service = Service(ChromeDriverManager().install())
        options = webdriver.ChromeOptions()
        # opcional: descomente se quiser exibir o browser ao usuário (recomendado para intervenção manual)
        # options.add_argument('--headless')
        driver = webdriver.Chrome(service=service, options=options)
        driver.maximize_window()
    except Exception as e:
        logger.exception("Falha ao inicializar ChromeDriver: %s", e)
        return

    try:
        # abre a página de login e aguarda o operador resolver captcha e clicar 'Cadastrar Apoiamento'
        driver.get(config.URL_LOGIN)
        logger.info("Abra o navegador, faça login e clique em 'Cadastrar Apoiamento'...")

        try:
            WebDriverWait(driver, config.INIT_WAIT_SECONDS).until(
                EC.url_contains(config.URL_TELA_CADASTRO)
            )
        except TimeoutException:
            logger.error("Tempo esgotado aguardando a tela de cadastro (login/CAPTCHA). Encerrando.")
            driver.quit()
            return

        logger.info("Tela de cadastro detectada. Iniciando loop automático.")

        while True:
            # detectar se operador clicou LIMPAR antes de iniciar
            if operador_clicou_limpar(driver):
                logger.info("Operador clicou LIMPAR. Encerrando e exibindo FIM DE PROCESSO.")
                mostrar_fim_de_processo()
                driver.quit()
                return

            registro = buscar_registro()
            if not registro:
                logger.info("Nenhum documento pendente. Aguardando %s segundos...", config.EMPTY_WAIT_SECONDS)
                time.sleep(config.EMPTY_WAIT_SECONDS)
                continue

            id_doc = registro[0]
            codigo = registro[1]
            logger.info("Processando documento id=%s codigo=%s", id_doc, codigo)

            caminho_pdf = baixar_pdf_ftp(codigo)
            if not caminho_pdf:
                logger.error("Não foi possível baixar o PDF para codigo=%s. Pulando registro.", codigo)
                # opcional: poderia marcar com erro no banco. Aqui seguimos para próximo.
                time.sleep(2)
                continue

            preencher_formulario(driver, registro, caminho_pdf)

            # clicar no cadastrar (robô sempre clica)
            try:
                driver.find_element(By.ID, config.ID_CADASTRAR).click()
                logger.info("Clicado em cadastrar para codigo=%s", codigo)
            except Exception as e:
                logger.error("Falha ao clicar cadastrar: %s", e)
                # decide o que fazer: pular ou tentar novamente

            # aguarda alguns segundos para o processamento do sistema
            time.sleep(config.AFTER_SUBMIT_WAIT_SECONDS)

            # marca registro como finalizado
            finalizar_registro(id_doc)

            # após cada ciclo, verificar se operador clicou LIMPAR
            if operador_clicou_limpar(driver):
                logger.info("Operador clicou LIMPAR após processamento. Encerrando.")
                mostrar_fim_de_processo()
                driver.quit()
                return

            # pequeno delay antes de próximo ciclo
            time.sleep(config.LOOP_DELAY_SECONDS)

    finally:
        try:
            driver.quit()
        except Exception:
            pass

if __name__ == '__main__':
    main()


# ------- FILE: config.py -------
# Crie um arquivo config.py ao lado do main.py com estas configurações.

# URL de login (página inicial com CAPTCHA)
URL_LOGIN = "https://sapf.tse.jus.br/sapf/paginas/principal.xhtml"
# trecho que identifica a tela de cadastro após o operador clicar em Cadastrar Apoiamento
URL_TELA_CADASTRO = "apoiamento/Criar"

# Pastas
PASTA_FICHAS = r"C:\RoboSAPF\fichas"

# Banco de dados PostgreSQL
DB_HOST = "localhost"
DB_USER = "usuario"
DB_PASS = "senha"
DB_NAME = "sua_base"

# FTP
FTP_HOST = "ftp.seuservidor.com"
FTP_USER = "ftpuser"
FTP_PASS = "ftppass"

# IDs dos elementos no formulário (conforme você informou)
ID_TIPO_APOIAMENTO = "tipoApoiamento:1"       # botão/checkbox
ID_UF = "ufLista_focus"
ID_NOME_APOIADOR = "nomeApoiador"
ID_TITULO_APOIADOR = "tituloApoiador"
ID_DATA_APOIAMENTO = "dataApoiamentoApoiador_input"
ID_ANALFABETO_SIM = "analfabetoApoiador:0"
ID_ANALFABETO_NAO = "analfabetoApoiador:1"
ID_NOME_AGENTE = "nomeAgente"
ID_TITULO_AGENTE = "tituloAgente"
ID_FILE_LABEL = "fileUp_label"
ID_CADASTRAR = "cadastrar"

# Comportamento / tempos (ajuste conforme ambiente)
INIT_WAIT_SECONDS = 600            # tempo máximo para o operador logar e abrir a tela (segundos)
EMPTY_WAIT_SECONDS = 10            # aguarda quando não há registros
AFTER_SUBMIT_WAIT_SECONDS = 4     # aguarda após clicar cadastrar
UPLOAD_WAIT_SECONDS = 3           # tempo para upload do arquivo
LOOP_DELAY_SECONDS = 1            # delay simples entre ciclos

# UF default (se precisar)
DEFAULT_UF = "RS"


# ------- FILE: README.txt -------
"""
Instruções para preparação do ambiente e compilação (.EXE)

1) Pré-requisitos (no Windows onde irá compilar):
   - Python 3.10 ou 3.11 instalado (Add to PATH marcado)
   - Google Chrome instalado

2) Estrutura de arquivos:
   C:\RoboSAPF\
       main.py
       config.py
       fichas\

3) Criar ambiente e instalar dependências:
   Abra o prompt de comando (cmd) e execute:

   cd C:\RoboSAPF
   pip install selenium webdriver-manager psycopg2-binary pyinstaller

   (psycopg2-binary recomendado para evitar problemas de compilação)

4) Ajustes no config.py:
   - Atualize DB_HOST, DB_USER, DB_PASS, DB_NAME
   - Atualize FTP_HOST, FTP_USER, FTP_PASS
   - Se necessário ajuste URL_TELA_CADASTRO
   - Ajuste tempos (opcionais)

5) Gerar o executável:
   No prompt, dentro de C:\RoboSAPF, execute:

   pyinstaller --onefile --noconsole main.py

   Após a compilação, o executável ficará em:
   C:\RoboSAPF\dist\main.exe

6) Entrega / Execução na máquina final:
   Copie para a máquina final apenas:
   - dist\main.exe
   - fichas\ (pasta vazia)
   - config.py (opcional: manter config.py na mesma pasta para ajustes sem recompilar)

   Execute main.exe. O Chrome será aberto. Faça login + CAPTCHA e clique em 'Cadastrar Apoiamento'.

7) Encerramento:
   Para encerrar a execução, clique no botão 'LIMPAR' no SAPF. A automação detectará isso, exibirá a janela "FIM DO PROCESSO" e aguardará você clicar SAIR.

8) Observações:
   - Se preferir, inclua icone.ico e use pyinstaller --onefile --noconsole --icon=icone.ico main.py
   - Se houver customizações no HTML (ids diferentes), atualize config.py

"""
