# ------- 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 logging
from ftplib import FTP
import psycopg2
import tkinter as tk
from datetime import datetime
import 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
from decimal import Decimal
import logging
import psycopg2

# 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
# ------------------------------------------------------------------

ftp = FTP(config.FTP_HOST, timeout=30)
ftp.login(config.FTP_USER, config.FTP_PASS)
logger.info("FTP PWD: %s", ftp.pwd())
logger.info("FTP LIST: %s", ftp.nlst())

def baixar_pdf_ftp(caminho_remoto):
    pasta = os.path.dirname(caminho_remoto)
    arquivo = os.path.basename(caminho_remoto)

    destino = os.path.join(PASTA_FICHAS, arquivo)

    try:
        ftp.cwd(pasta)

        with open(destino, "wb") as f:
            ftp.retrbinary(f"RETR {arquivo}", f.write)

        logger.info("PDF baixado: %s", destino)
        return destino

    except Exception as e:
        logger.exception("Erro ao baixar PDF %s", caminho_remoto)
        return None

def scroll_para(driver, elemento):
    driver.execute_script(
        "arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});",
        elemento
    )

def limpar_formulario(driver):
    try:
        botao = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.ID, "limpar"))
        )

        driver.execute_script(
            "arguments[0].scrollIntoView({block: 'center'});", botao
        )
        time.sleep(0.3)

        botao.click()
        logger.info("Formulário limpo com sucesso.")

        # Aguarda o formulário resetar de verdade
        WebDriverWait(driver, 10).until(
            EC.text_to_be_present_in_element_value(
                (By.ID, config.ID_NOME_APOIADOR), ""
            )
        )

        time.sleep(0.5)

    except Exception as e:
        logger.error("Falha ao clicar em LIMPAR: %s", e)

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.get(sigla)
    if not uf_id:
        raise ValueError(f"UF inválida: {sigla}")
    logger.info("Sigla %s valor %s", sigla, uf_id)
    return uf_id

def buscar_registros_pendentes():
    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
                partidt035,
                titelet035,
                cpfxxxt035,
                nompest035,
                unifedt035,
                analfat035,
                nomcolt035,
                titcolt035,
                datapot035,
                horaxxt035,
                pastaxt035,
                serialt035
            FROM dsr035
            WHERE statust035 = 1
            ORDER BY serialt035
        """)

        registros = cur.fetchall()
        logger.info("Registros pendentes encontrados: %s", len(registros))

        cur.close()
        conn.close()
        return registros

    except Exception as e:
        logger.error("Erro ao buscar registros pendentes: %s", e)
        return []

def marcar_como_em_processamento(serialt035):
    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 dsr035
            SET statust035 = 9
            WHERE serialt035 = %s AND statust035 = 1
        """, (serialt035,))
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        logger.error("Erro ao marcar em processamento %s: %s", serialt035, e)

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

def normalizar_titulo(titulo):
    if titulo is None:
        return ""
    return str(titulo).replace('.', '').strip().zfill(12)

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)
    
    if isinstance(titelct030, Decimal):
        titelct030 = format(titelct030, 'f')

    if isinstance(COLETORTITULO, Decimal):
        COLETORTITULO = format(COLETORTITULO, 'f')

    if isinstance(data_apoio, Decimal):
        data_apoio = str(data_apoio)

    if isinstance(data_apoio, str):
        # assume YYYYMMDD
        data_apoio = datetime.strptime(data_apoio, "%Y%m%d").strftime("%d/%m/%Y")

    titelct030 = str(titelct030).strip()
    COLETORTITULO = str(COLETORTITULO).strip()

    titelct030 = normalizar_titulo(titelct030)
    COLETORTITULO = normalizar_titulo(COLETORTITULO)

    logger.info("TÍTULO APOIADOR NORMALIZADO: %s", titelct030)
    logger.info("TÍTULO AGENTE NORMALIZADO: %s", 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")
        element = driver.find_element(By.ID, "ufLista_label")
        scroll_para(driver, element)
        #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:
        element = driver.find_element(By.ID, config.ID_NOME_APOIADOR)
        scroll_para(driver, element)
        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:
        element = driver.find_element(By.ID, config.ID_TITULO_APOIADOR)
        scroll_para(driver, element)
        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, str(data_apoio))
    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:
        element = driver.find_element(By.ID, config.ID_NOME_AGENTE)
        scroll_para(driver, element)
        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:
        element = driver.find_element(By.ID, config.ID_TITULO_AGENTE)
        scroll_para(driver, element)        
        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

def finalizar_registro(serialt035):
    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 dsr035
            SET statust035 = 2,
                sapdatt035 = %s,
                saphort035 = %s
            WHERE serialt035 = %s
        """, (
            datetime.today().strftime('%Y%m%d'),
            datetime.now().strftime('%H%M%S'),
            serialt035
        ))

        conn.commit()
        cur.close()
        conn.close()

        logger.info("Registro %s finalizado com sucesso", serialt035)

    except Exception as e:
        logger.error("Erro ao finalizar registro %s: %s", serialt035, e)

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

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

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():
    CHECK_INTERVAL = 5
    driver = None

    logger.info("fase 1")
    # inicia chrome
    try:
        service = Service(ChromeDriverManager().install())
        options = webdriver.ChromeOptions()
        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:
        # login manual
        driver.get(config.URL_LOGIN)
        logger.info("Aguardando login manual...")

        WebDriverWait(driver, config.INIT_WAIT_SECONDS).until(
            EC.url_contains(config.URL_TELA_CADASTRO)
        )

        logger.info("Tela de cadastro detectada.")

        while True:
            if not operador_nao_clicou_cadastrarApoiamento(driver):
                logger.info("Operador clicou em Cadastrar Apoiamento.")
                break

            logger.debug("Ainda aguardando clique do operador...")
            time.sleep(1)

            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)

        while True:
            registros = buscar_registros_pendentes()

            if not registros:
                logger.info("Nenhum pendente. Aguardando...")
                time.sleep(CHECK_INTERVAL)
                continue

            for registro in registros:
                serialt035 = None
                try:
                    (
                        partidt035,
                        titelet035,
                        cpfxxxt035,
                        nompest035,
                        unifedt035,
                        analfat035,
                        nomcolt035,
                        titcolt035,
                        datapot035,
                        horaxxt035,
                        pastaxt035,
                        serialt035
                    ) = registro

                    logger.info(
                        "Processando CPF=%s Título=%s UF=%s",
                        cpfxxxt035, titelet035, unifedt035
                    )

                    marcar_como_em_processamento(serialt035)
                    caminho_pdf_local = baixar_pdf_ftp(pastaxt035)
                    ftp.cwd("/")

                    if not caminho_pdf_local:
                        logger.error("Falha ao baixar PDF: %s", pastaxt035)
                        continue

                    preencher_formulario(
                        driver,
                        caminho_pdf_local,
                        serialt035,
                        analfat035,
                        cpfxxxt035,
                        nompest035,
                        titelet035,
                        unifedt035,
                        datapot035,
                        nomcolt035,
                        titcolt035
                    )

                    # clicar cadastrar
                    botao = WebDriverWait(driver, 10).until(
                        EC.element_to_be_clickable((By.ID, config.ID_CADASTRAR))
                    )
                    driver.execute_script(
                        "arguments[0].scrollIntoView({block: 'center'});", botao
                    )
                    time.sleep(0.5)
                    botao.click()
                    logger.info("Clicado em cadastrar.")

                    # aguarda mensagem growl
                    try:
                        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()

                        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.lower():
                            logger.info("SUCESSO: %s", texto_final)
                            finalizar_registro(serialt035)

                            limpar_formulario(driver)
                        else:
                            logger.error("ERRO/ALERTA: %s", texto_final)
                            limpar_formulario(driver)

                    except TimeoutException:
                        logger.error("Nenhuma mensagem Growl apareceu.")

                    if operador_clicou_limpar(driver):
                        logger.info("Operador clicou LIMPAR. Encerrando.")
                        return

                    time.sleep(config.LOOP_DELAY_SECONDS)

                except Exception as e:
                    logger.exception("Erro ao processar registro %s: %s", serialt035, e)

    finally:
        if driver:
            driver.quit()

if __name__ == '__main__':
    main()