# ------- 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 as t
from datetime import datetime, time
import logging
from ftplib import FTP
import psycopg2
import tkinter as tk
from datetime import datetime, time

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
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select

# 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(
    filename=r"C:\des-py\RoboSAPF\robosapf.log",
    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 set_data_apoiamento(driver, data_str):
    """
    Define diretamente a data no calendário PrimeFaces.
    Exemplo de data_str: '14/11/2025'
    """

    # Localiza o campo
    
        # Garante que o elemento existe
    campo = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "dataApoiamentoApoiador_input"))
    )

    # Bypass total: remove readonly, seta valor e dispara eventos
    driver.execute_script("""
        const el = arguments[0];
        el.removeAttribute('readonly');
        el.removeAttribute('aria-readonly');

        // Seta o valor diretamente (mais estável que send_keys)
        el.value = arguments[1];

        // Dispara eventos que o datepicker/primefaces usa
        el.dispatchEvent(new Event('input', { bubbles: true }));
        el.dispatchEvent(new Event('change', { bubbles: true }));
        el.dispatchEvent(new Event('blur', { bubbles: true }));
    """, campo, data_str)

    time.sleep(0.2)
    '''
    campo = driver.find_element(By.ID, "dataApoiamentoApoiador_input")

    # Remove o atributo readonly via JS
    driver.execute_script("""
        arguments[0].removeAttribute('readonly');
        arguments[0].removeAttribute('aria-readonly');
    """, campo)
    
    time.sleep(0.3)

    # Limpa e insere a data desejada
    campo.click()
    campo.clear()
    campo.send_keys(data_str)

    # Opcional: dispara eventos para o PrimeFaces reconhecer o valor
    driver.execute_script("""
        const el = arguments[0];
        el.dispatchEvent(new Event('change', { bubbles: true }));
        el.dispatchEvent(new Event('blur', { bubbles: true }));
    """, campo)

    # Confirma com TAB (muitos formulários exigem isso)
    campo.send_keys(Keys.TAB)
    '''

def selecionar_uf(driver, sigla):
    """
    Preenche o combo ufLista_input a partir da sigla da UF (ex: 'RS', 'SP').
    """
    
    mapa_uf = {
        "AC": "UF2082",
        "AL": "UF2091",
        "AM": "UF2092",
        "AP": "UF2095",
        "BA": "UF2111",
        "CE": "UF2146",
        "DF": "UF2178",
        "ES": "UF2222",
        "GO": "UF2280",
        "MA": "UF2452",
        "MG": "UF2458",
        "MS": "UF2470",
        "MT": "UF2471",
        "PA": "UF2545",
        "PB": "UF2546",
        "PE": "UF2549",
        "PI": "UF2553",
        "PR": "UF2562",
        "RJ": "UF2616",
        "RN": "UF2620",
        "RO": "UF2621",
        "RR": "UF2624",
        "RS": "ufLista_23",
        "SC": "UF2640",
        "SE": "UF2642",
        "SP": "UF2653",
        "TO": "UF2683",
    }
    
    uf_id = mapa_uf[sigla]
    logger.info("Sigla %s valor %s", sigla, uf_id)
    return uf_id

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 nrodoct030, cpfxxxt030, tipdoct030, nomdoct030, dataupt030, horaupt030, 
                   titelct030, anoxxxt030, turnoxt030, unifedt030, tokenxt030, zonelet030, secaoxt030, 
                   serialt030, sapfxxt030, datlant030, horlant030,
                   (SELECT nompest000 FROM dsr000 WHERE cpfxxxt000 = cpfxxxt030)
            FROM dsr030
            WHERE tipdoct030 = 1  -- Documento Publico
            AND   nrodoct030 = 1  -- PARTIDO CONSERVADOR
            AND   sapfxxt030 = 0  -- Documento não processado pelo SAPF
            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, caminho_pdf, serialt030, analfat030, cpfxxxt030, nompest010, titelct030, unifedt030, data_apoio, COLETORNOME, COLETORTITULO):

    logger.info("Preenchendo formulário para CPF=%s", cpfxxxt030)
    logger.info("NOME %s TITULO %s", COLETORNOME, COLETORTITULO)

    # tipoApoiamento:1 (clicar opção)
    try:
        label_icp = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "label[for='tipoApoiamento:1']"))
        )
        label_icp.click()
        logger.info("Marcada a opção: Assinatura eletrônica ICP-Brasil")
    except Exception as e:
        logger.warning("Não conseguiu clicar na opção tipoApoiamento: %s", e)

    # UF - combo de foco
    try:
        logger.info(unifedt030)
        uf_id = selecionar_uf(driver, unifedt030)
        
        #element = driver.find_element(By.ID, "ufLista_input")
        #select.select_by_value(uf_id)
        driver.find_element(By.ID, "ufLista_label").click()
        driver.find_element(By.ID, uf_id).click()
        
    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(nompest010 or '')
    except Exception as e:
        logger.warning("Não conseguiu preencher nomeApoiador: %s", e)

    # Título do apoiador
    logger.info("titulo %s", titelct030)
    try:
        driver.find_element(By.ID, config.ID_TITULO_APOIADOR).click()
        driver.find_element(By.ID, config.ID_TITULO_APOIADOR).send_keys(titelct030)
    except Exception as e:
        logger.warning("Não conseguiu preencher tituloApoiador: %s", e)

    # Data - espera formatar caso seja date
    try:
        set_data_apoiamento(driver, "15/11/2025")
    except Exception as e:
        logger.warning("Não conseguiu preencher dataApoio: %s", e)

    # Analfabeto - assumir radio with suffix ; try both options
    if (analfat030 == 1):
        try:
            label_sim = WebDriverWait(driver, 10).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, "label[for='analfabetoApoiador:0']"))
            )
            label_sim.click()
            logger.info("Marcada a opção 'Sim' (alfabetizado).")
        except Exception as e:
                logger.warning("Não conseguiu clicar na opção 'Sim' de analfabetoApoiador: %s", e)
    else:
        try:
            label_nao = WebDriverWait(driver, 10).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, "label[for='analfabetoApoiador:1']"))
            )
            label_nao.click()
            logger.info("Marcada a opção 'Não' (alfabetizado).")
        except Exception as e:
                logger.warning("Não conseguiu clicar na opção 'Não' de analfabetoApoiador: %s", e)
    
    # Nome agente e título agente
    try:
        driver.find_element(By.ID, config.ID_NOME_AGENTE).send_keys(COLETORNOME)
    except Exception as e:
        logger.warning("Não conseguiu preencher Nome do responsável pela coleta: %s", e)
    
    try:        
        driver.find_element(By.ID, config.ID_TITULO_AGENTE).click()
        driver.find_element(By.ID, config.ID_TITULO_AGENTE).send_keys(COLETORTITULO)
    except Exception as e:
        logger.warning("Não conseguiu preencher Título do Eleitor do responsável pela coleta: %s", e)

    # Upload
    logger.info("Upload iniciado: %s", caminho_pdf)
    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 cpfxxxt030

# ------------------------------------------------------------------
# Marcar registro como enviado
# ------------------------------------------------------------------
def finalizar_registro(cpfxxxt030):
    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 dsr030 SET sapfxxt030 = 2 WHERE serialt030 = %s", (serialt030))
        conn.commit()
        cur.close()
        conn.close()
        logger.info("Registro %s marcado como ENVIADO", cpfxxxt030)
    except Exception as e:
        logger.error("Erro ao atualizar registro %s: %s", cpfxxxt030, e)

# ------------------------------------------------------------------
# Detecta se o operador clicou no LIMPAR (botão 'cadastrar' desapareceu)
# ------------------------------------------------------------------
def operador_clicou_limpar(driver):
    try:
        # se a tela ainda não carregou, não encerra
        WebDriverWait(driver, 2).until(
            EC.presence_of_element_located((By.ID, config.ID_CADASTRAR))
        )
        return False
    except TimeoutException:
        # só vamos encerrar se ja estávamos no formulário anteriormente
        return False
    except NoSuchElementException:
        # só encerra se REALMENTE o usuário clicou limpar
        return True

# ------------------------------------------------------------------
# Detecta se o operador clicou no cadastrarApoiamento
# ------------------------------------------------------------------
def operador_nao_clicou_cadastrarApoiamento(driver):
    try:
        driver.find_element(By.ID, config.ID_CADASTRAR_APOIAMENTO).click()
        return False
            
    except NoSuchElementException:
        return True
    except WebDriverException as e:
        logger.error("Erro webdriver ao checar element: %s", e)
        return True

# ------------------------------------------------------------------
# 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
    logger.info("fase 1")
    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
    
    logger.info("fase 2")
    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("fase 3")
         # detectar se operador clicou cadastrarApoiamento antes de iniciar
         
        while operador_nao_clicou_cadastrarApoiamento(driver) == True:
            x = 0
            #logger.info("Aguardando clicar cadastrarApoiamento.")
            #return
                
        while True:
            data_apoio = datetime.today()
            data_apoio_inv = data_apoio.strftime('%Y%m%d')
            hora_apoio = datetime.now().time()

            logger.info("data_apoio normal %s invertida %s, hora_apoio %s", data_apoio, data_apoio_inv, hora_apoio)
#           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

            registro = [None] * 19
            registro[0] = 1
            registro[1] = 3770736010
            registro[3] = r"Ficha apoiamento"
            registro[6] = r"019899100469"
            registro[9] = r"RS"
            registro[10] = r"7TrQWGkYfu7BkAB0XO8ZUSJEYTk2mV3L"
            registro[11] = 7          
            registro[13] = 123
            registro[14] = 0
            registro[15] = 0
            registro[16] = 0
            registro[17] = 0
            registro[18] = r"João Cláudio Pêgas de Brito Rochenbach"  
            
            logger.info("fase 4")
            
            nrodoct030 = registro[0]
            cpfxxxt030 = registro[1]
            nomdoct030 = registro[3]
            titelct030 = registro[6]
            unifedt030 = registro[9]
            tokenxt030 = registro[10]
            zonelet030 = registro[11]          
            serialt030 = registro[13]
            analfat030 = registro[14]
            sapfxxt030 = registro[15]
            datlant030 = registro[16]
            horlant030 = registro[17]
            nompest010 = registro[18]
            
            logger.info("Processando documento CPF=%s Título=%s Estado=%s", cpfxxxt030, titelct030, unifedt030)
            #mostrar_fim_de_processo()

            caminho_pdf = r"0377073601017TrQWGkYfu7BkAB0XO8ZUSJEYTk2mV3L.pdf"
# ATIVAR FTP
#            caminho_pdf = baixar_pdf_ftp(cpfxxxt030)
#            if not caminho_pdf:
#               logger.error("Não foi possível baixar o PDF para CPF=%s. Pulando registro.", cpfxxxt030)
#               # ATUALIZAR sapfxxt030 = 7 - Erro ao baixar o Documento 
#               # opcional: poderia marcar com erro no banco. Aqui seguimos para próximo.
#               time.sleep(2)
#               continue
            cpf = str(cpfxxxt030)
            numerodoc = str(nrodoct030)
            token = str(tokenxt030)
            
            COLETORNOME = config.COLETOR_NOME
            COLETORTITULO = config.COLETOR_TITULO
            
            #numdoc 6 digitos e CPF com 11
            caminho_pdf = r"C:\PORTAL\tse\conservador\0377073601017TrQWGkYfu7BkAB0XO8ZUSJEYTk2mV3L.pdf"
            #caminho_pdf = r"C:\PORTAL\tse\conservador\0%s1%s%s.pdf" % (cpf, numerodoc, token)
            
            preencher_formulario(driver, caminho_pdf, serialt030, analfat030, cpfxxxt030,
            nompest010, titelct030, unifedt030, data_apoio, COLETORNOME, COLETORTITULO)
            
            # clicar no cadastrar (robô sempre clica)
            
                       # clicar no cadastrar (robô sempre clica)
            try:
                botao = WebDriverWait(driver, 10).until(
                    EC.element_to_be_clickable((By.ID, config.ID_CADASTRAR))
                )
                driver.execute_script(
                    "arguments[0].scrollIntoView({block: 'center'});", botao
                )
                t.sleep(0.5)
                botao.click()
                logger.info("Clicado em cadastrar.")
            except Exception as e:
                logger.error("Falha ao clicar cadastrar: %s", e)

            # finalizar_registro(cpfxxxt030)

            try:
                # Aguarda o growl aparecer
                msg_title = WebDriverWait(driver, 10).until(
                    EC.presence_of_element_located((By.CSS_SELECTOR, ".ui-growl-item .ui-growl-title"))
                )
                title_text = msg_title.text.strip()

                # Em alguns casos, existe também uma mensagem complementar
                try:
                    msg_detail = driver.find_element(
                        By.CSS_SELECTOR, ".ui-growl-item .ui-growl-message"
                    ).text.strip()
                except Exception:
                    msg_detail = ""

                texto_final = f"{title_text} {msg_detail}".strip()

                if "Sucesso" in texto_final or "sucesso" in texto_final:
                    logger.info(f"MENSAGEM DE SUCESSO: {texto_final}")
                else:
                    logger.error(f"MENSAGEM DE ERRO/ALERTA: {texto_final}")

            except TimeoutException:
                logger.error("Nenhuma mensagem Growl apareceu após enviar o formulário.")
            except Exception as e:
                logger.error(f"Falha ao capturar mensagem do Growl: {e}")

            # 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()