# -*- coding: utf-8 -*-

from datetime import datetime, timedelta, timezone
from email.message import EmailMessage
from pathlib import Path
import shutil
import smtplib
import subprocess
import time
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

try:
    import mysql.connector
except ImportError:
    mysql = None
else:
    mysql = mysql.connector

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
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 webdriver_manager.chrome import ChromeDriverManager
from selenium.common.exceptions import NoAlertPresentException, TimeoutException


DEBUG = True
USAR_BANCO = True
TESTAR_SOMENTE_BANCO = False
LIMITE_COLETAS = 5
DIAS_RETROATIVOS = 0
PAUSA_ENTRE_COLETAS = 8
PAUSA_APOS_CLIQUE_COLETAR = 20
PAUSA_APOS_CONFIRMACAO_COLETA = 20
PAUSA_REQUISICAO_ANDAMENTO = 8
MAX_TENTATIVAS_PESQUISA = 3
EMAIL_ALERTA_ATIVO = True
EMAIL_DESTINATARIO = "suporte@kreativesistemas.com.br"
EMAIL_REMETENTE = "suporte@kreativesistemas.com.br"
INTERVALO_ALERTA_FATAL_SEGUNDOS = 30 * 60

# Se SMTP_HOST ficar vazio, no servidor Linux o script tenta usar sendmail.
SMTP_HOST = ""
SMTP_PORT = 587
SMTP_USUARIO = ""
SMTP_SENHA = ""
SMTP_USAR_TLS = True
try:
    TIMEZONE = ZoneInfo("America/Sao_Paulo")
except ZoneInfoNotFoundError:
    TIMEZONE = timezone(timedelta(hours=-3))

# ==============================
# CONFIGURACOES DE LOGIN SSW
# ==============================

DOMINIO = "BMO"
CPF = "25594231897"
USUARIO = "fabio"
SENHA = "26200209"

# ==============================
# CONFIGURACOES DO BANCO
# ==============================

DB_CONFIG = {
    "host": "177.234.148.42",
    "user": "ksoftlogcom_user",
    "password": "a7850254fdac",
    "database": "ksoftlogcom_bemol",
}

# Use este bloco para testar localmente sem banco.
# No servidor, altere USAR_BANCO para True.
COLETAS_TESTE = [
    {"Cod": 1, "filial": "CWB", "Cte": "063608"},
    {"Cod": 2, "filial": "CWB", "Cte": "063603"},
    {"Cod": 3, "filial": "CWB", "Cte": "063613"},
]

# ==============================
# CONFIGURACOES DO SSW
# ==============================

PROGRAMA = "003"
URL_LOGIN = "https://sistema.ssw.inf.br/bin/ssw0422"
URL_MENU = "https://sistema.ssw.inf.br/bin/menu01"


def conectar_banco():
    if mysql is None:
        raise RuntimeError(
            "Modulo mysql-connector-python nao encontrado. "
            "Instale com: pip install mysql-connector-python"
        )

    return mysql.connect(**DB_CONFIG)


def periodo_busca():
    hoje = datetime.now(TIMEZONE).date()
    data_inicial = hoje - timedelta(days=DIAS_RETROATIVOS)
    return data_inicial.isoformat(), hoje.isoformat()


def buscar_coletas_pendentes(conn, limite=LIMITE_COLETAS):
    data_inicial, data_final = periodo_busca()

    sql_filial = """
        SELECT filial
        FROM itensromaneio
        WHERE status IN ('coletado')
          AND coletado = '0'
          AND DataEmissao BETWEEN %s AND %s
          AND tipomov = 'COL'
          AND filial != 'FEC'
          AND data_alteracao <= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
        ORDER BY data_alteracao ASC
        LIMIT 1
    """

    cursor = conn.cursor(dictionary=True)
    cursor.execute(sql_filial, (data_inicial, data_final))
    filial_lote = cursor.fetchone()

    if not filial_lote:
        cursor.close()
        return []

    sql = """
        SELECT
            Cod,
            Cte,
            filial,
            DataEmissao,
            data_alteracao
        FROM itensromaneio
        WHERE status IN ('coletado')
          AND coletado = '0'
          AND DataEmissao BETWEEN %s AND %s
          AND tipomov = 'COL'
          AND filial != 'FEC'
          AND data_alteracao <= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
          AND filial = %s
        ORDER BY data_alteracao ASC
        LIMIT %s
    """

    cursor.execute(sql, (data_inicial, data_final, filial_lote["filial"], limite))
    coletas = cursor.fetchall()
    cursor.close()

    return coletas


def marcar_coleta_baixada(conn, cod):
    if not conn.is_connected():
        print("Conexao MySQL indisponivel. Reconectando antes do UPDATE...")
        conn.reconnect(attempts=3, delay=2)

    sql = """
        UPDATE itensromaneio
        SET coletado = '1'
        WHERE Cod = %s
        LIMIT 1
    """

    cursor = conn.cursor()
    cursor.execute(sql, (cod,))
    conn.commit()
    cursor.close()


def marcar_coleta_nao_baixada(conn, cod):
    if not conn.is_connected():
        print("Conexao MySQL indisponivel. Reconectando antes de marcar a falha...")
        conn.reconnect(attempts=3, delay=2)

    sql = """
        UPDATE itensromaneio
        SET coletado = '2'
        WHERE Cod = %s
          AND coletado = '0'
        LIMIT 1
    """

    cursor = conn.cursor()
    cursor.execute(sql, (cod,))
    conn.commit()
    cursor.close()


def enviar_mensagem_email(assunto, corpo):
    mensagem = EmailMessage()
    mensagem["From"] = EMAIL_REMETENTE
    mensagem["To"] = EMAIL_DESTINATARIO
    mensagem["Subject"] = assunto
    mensagem.set_content(corpo)

    try:
        if SMTP_HOST:
            with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as smtp:
                if SMTP_USAR_TLS:
                    smtp.starttls()
                if SMTP_USUARIO:
                    smtp.login(SMTP_USUARIO, SMTP_SENHA)
                smtp.send_message(mensagem)
        else:
            sendmail = shutil.which("sendmail") or "/usr/sbin/sendmail"
            subprocess.run(
                [sendmail, "-t", "-oi"],
                input=mensagem.as_bytes(),
                check=True,
                timeout=30,
            )

        print(f"Alerta enviado para {EMAIL_DESTINATARIO}.")
        return True

    except Exception as exc:
        print(f"Nao foi possivel enviar alerta por email: {exc}")
        return False


def enviar_alerta_email(cod, filial, numero_coleta, erro, retirada_fila=False):
    if not EMAIL_ALERTA_ATIVO:
        return

    assunto = f"[SSW] Coleta nao baixada - {numero_coleta}"
    situacao_integracao = (
        "Marcada como nao baixada (coletado = 2) e retirada da fila automatica."
        if retirada_fila
        else "Nao foi possivel retirar automaticamente esta coleta da fila; requer verificacao."
    )
    corpo = f"""A coleta nao pode ser baixada apos as tentativas configuradas.

Cod: {cod}
Filial: {filial}
Coleta/Cte: {numero_coleta}
Data/hora: {datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}

Erro:
{erro}

Situacao na integracao: {situacao_integracao}
"""
    enviar_mensagem_email(assunto, corpo)


def enviar_alerta_fatal(erro):
    if not EMAIL_ALERTA_ATIVO:
        return

    arquivo_controle = Path(__file__).resolve().with_name(".baixar_coletas_alerta_fatal")
    agora = time.time()
    try:
        ultimo_alerta = float(arquivo_controle.read_text(encoding="utf-8").strip())
    except (OSError, ValueError):
        ultimo_alerta = 0

    restante = INTERVALO_ALERTA_FATAL_SEGUNDOS - (agora - ultimo_alerta)
    if restante > 0:
        print(f"Alerta fatal suprimido pelo intervalo de 30 minutos ({int(restante)}s restantes).")
        return

    assunto = "[SSW] Automacao de coletas interrompida"
    corpo = f"""A automacao SSW de baixa de coletas foi interrompida antes de concluir o processamento.

Arquivo: {Path(__file__).name}
Data/hora: {datetime.now(TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}
Novo alerta permitido apos: 30 minutos

Erro:
{erro}
"""
    if enviar_mensagem_email(assunto, corpo):
        arquivo_temporario = arquivo_controle.with_suffix(".tmp")
        try:
            arquivo_temporario.write_text(str(agora), encoding="utf-8")
            arquivo_temporario.replace(arquivo_controle)
        except OSError as exc:
            print(f"Nao foi possivel atualizar o controle do alerta fatal: {exc}")


def criar_driver():
    options = Options()

    if DEBUG:
        options.add_experimental_option("detach", True)
    else:
        options.add_argument("--headless=new")

    options.add_argument("--window-size=1200,800")
    options.add_argument("--disable-gpu")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")

    return webdriver.Chrome(
        service=Service(ChromeDriverManager().install()),
        options=options,
    )


def obter_data_coletada(driver):
    return driver.execute_script("""
        const dataRegex = /\\b\\d{2}\\/\\d{2}\\/\\d{2}\\s+\\d{2}:\\d{2}\\b/;
        const px = (value) => Number(String(value || '').replace('px', '')) || 0;
        const labels = Array.from(document.querySelectorAll('div.texto'));
        const label = labels.find((el) =>
            (el.textContent || '').replace(/\\u00a0/g, ' ').includes('Coletada')
        );

        if (!label) {
            return '';
        }

        const top = label.style.top;
        const labelLeft = px(label.style.left);
        const dataMesmoTop = Array.from(document.querySelectorAll('div.data')).find((el) =>
            el.style.top === top &&
            px(el.style.left) > labelLeft &&
            dataRegex.test((el.textContent || '').trim())
        );

        if (dataMesmoTop) {
            return dataMesmoTop.textContent.trim();
        }

        let proximo = label.nextElementSibling;
        while (proximo) {
            const texto = (proximo.textContent || '').trim();
            if (
                proximo.classList &&
                proximo.classList.contains('data') &&
                dataRegex.test(texto)
            ) {
                return texto;
            }
            proximo = proximo.nextElementSibling;
        }

        return '';
    """)


def realizar_login(driver):
    print("Acessando tela de login...")
    driver.get(URL_LOGIN)
    time.sleep(5)

    print("Realizando login...")
    driver.find_element(By.ID, "1").send_keys(DOMINIO)
    driver.find_element(By.ID, "2").send_keys(CPF)
    driver.find_element(By.ID, "3").send_keys(USUARIO)
    driver.find_element(By.ID, "4").send_keys(SENHA)
    driver.find_element(By.ID, "5").click()
    time.sleep(5)

    print("Login efetuado com sucesso.")


def abrir_programa_003(driver, filial):
    print("Acessando menu principal...")
    driver.get(URL_MENU)
    time.sleep(3)

    janela_menu = driver.current_window_handle

    print(f"Selecionando filial: {filial}")
    campo_filial = driver.find_element(By.ID, "2")
    campo_filial.clear()
    campo_filial.send_keys(filial)

    driver.execute_script(
        "arguments[0].dispatchEvent(new Event('change'));",
        campo_filial,
    )

    print("Filial atual:", campo_filial.get_attribute("value"))
    time.sleep(1)

    print(f"Informando programa: {PROGRAMA}")
    campo_programa = driver.find_element(By.ID, "3")
    campo_programa.clear()
    campo_programa.send_keys(PROGRAMA)
    time.sleep(2)

    try:
        alerta = driver.switch_to.alert
        print("ALERTA ENCONTRADO:")
        print(alerta.text)
        alerta.accept()
        print("Alerta fechado automaticamente.")
    except NoAlertPresentException:
        print("Nenhum alerta encontrado.")

    print("Aguardando abertura do SSW0018...")

    WebDriverWait(driver, 20).until(lambda navegador: len(navegador.window_handles) > 1)

    for handle in driver.window_handles:
        if handle != janela_menu:
            driver.switch_to.window(handle)
            break

    janela_coleta = driver.current_window_handle

    print("Janela SSW0018 aberta.")
    print("URL atual:", driver.current_url)

    if "ssw0018" not in driver.current_url.lower():
        print("Aviso: URL inesperada.")
        print(driver.current_url)

    return janela_menu, janela_coleta


def fechar_janelas_detalhe(driver, janela_menu, janela_coleta):
    janelas_fixas = {janela_menu, janela_coleta}
    janelas_para_fechar = [
        handle for handle in driver.window_handles
        if handle not in janelas_fixas
    ]

    for handle in janelas_para_fechar:
        try:
            driver.switch_to.window(handle)
            aceitar_alerta_se_existir(driver)
            driver.close()
            print("Janela de detalhe fechada.")
        except Exception as exc:
            print(f"Nao foi possivel fechar uma janela de detalhe: {exc}")

    if janela_coleta in driver.window_handles:
        driver.switch_to.window(janela_coleta)
    elif janela_menu in driver.window_handles:
        driver.switch_to.window(janela_menu)

    aceitar_alerta_se_existir(driver)


def fechar_janelas_ssw(driver):
    for handle in list(driver.window_handles):
        try:
            driver.switch_to.window(handle)
            aceitar_alerta_se_existir(driver)
            driver.close()
        except Exception as exc:
            print(f"Nao foi possivel fechar uma janela do SSW: {exc}")


def aceitar_alerta_se_existir(driver):
    try:
        alerta = driver.switch_to.alert
        texto = alerta.text
        print("ALERTA ENCONTRADO:")
        print(texto)
        alerta.accept()
        print("Alerta fechado automaticamente.")
        return texto
    except NoAlertPresentException:
        return None


def confirmar_modal_sim_se_existir(driver, timeout=PAUSA_APOS_CLIQUE_COLETAR):
    fim = time.time() + timeout

    while time.time() < fim:
        modal = driver.execute_script("""
            const box = document.getElementById('errormsg');
            if (!box || box.style.visibility === 'hidden') {
                return null;
            }

            const texto = (box.innerText || '').replace(/\\s+/g, ' ').trim();
            const botoes = Array.from(box.querySelectorAll('a.dialog'));
            const botaoSim = botoes.find((el) => (el.textContent || '').trim().includes('Sim'));

            if (!botaoSim) {
                return {encontrado: true, clicado: false, texto};
            }

            botaoSim.click();
            return {encontrado: true, clicado: true, texto};
        """)

        if modal and modal.get("encontrado"):
            print("Modal de confirmacao encontrado:")
            print(modal.get("texto", ""))

            if modal.get("clicado"):
                print("Opcao 'Sim' selecionada automaticamente.")
                time.sleep(2)
                return True

            print("Modal encontrado, mas o botao 'Sim' nao foi localizado.")
            return False

        time.sleep(1)

    return False


def tela_detalhe_carregada(driver):
    url = driver.current_url.lower()

    if "blank.html" in url:
        return False

    return driver.execute_script("""
        const texto = document.body ? (document.body.innerText || '') : '';
        return Boolean(
            document.getElementById('link_col') ||
            texto.includes('Coletada')
        );
    """)


def pesquisar_coleta(driver, janela_menu, janela_coleta, numero_coleta):
    driver.switch_to.window(janela_coleta)

    for tentativa in range(1, MAX_TENTATIVAS_PESQUISA + 1):
        aceitar_alerta_se_existir(driver)

        print(f"\nInformando coleta: {numero_coleta}")
        campo_coleta = WebDriverWait(driver, 20).until(
            EC.presence_of_element_located((By.ID, "7"))
        )
        campo_coleta.clear()
        campo_coleta.send_keys(str(numero_coleta).zfill(6))

        print("Coleta informada:", campo_coleta.get_attribute("value"))
        time.sleep(1)

        handles_anteriores = set(driver.window_handles)

        print(
            "Clicando na seta para pesquisar a coleta "
            f"(tentativa {tentativa}/{MAX_TENTATIVAS_PESQUISA})..."
        )
        botao_avancar = driver.find_element(By.ID, "8")
        driver.execute_script("arguments[0].click();", botao_avancar)
        time.sleep(1)

        alerta = aceitar_alerta_se_existir(driver)

        if alerta:
            print(
                "SSW ainda tem requisicao em andamento. "
                f"Aguardando {PAUSA_REQUISICAO_ANDAMENTO}s para tentar novamente..."
            )
            time.sleep(PAUSA_REQUISICAO_ANDAMENTO)
            driver.switch_to.window(janela_coleta)
            continue

        print("Aguardando abertura da tela da coleta...")

        for _ in range(20):
            handles_novos = [h for h in driver.window_handles if h not in handles_anteriores]
            if handles_novos:
                janela_detalhe = handles_novos[0]
                driver.switch_to.window(janela_detalhe)
                print("Tela da coleta aberta em nova janela.")
                print("URL atual:", driver.current_url)

                try:
                    WebDriverWait(driver, 25).until(tela_detalhe_carregada)
                    print("Tela de detalhe carregada.")
                    print("URL atual:", driver.current_url)
                    return janela_detalhe
                except TimeoutException:
                    print("Tela de detalhe ficou em branco ou sem conteudo esperado.")
                    driver.close()
                    driver.switch_to.window(janela_coleta)
                    time.sleep(PAUSA_REQUISICAO_ANDAMENTO)
                    break
            time.sleep(1)

        print("Tela de detalhe nao abriu nessa tentativa.")

    raise Exception(
        "Nao foi possivel abrir a tela de detalhe da coleta apos "
        f"{MAX_TENTATIVAS_PESQUISA} tentativas."
    )


def baixar_coleta(driver):
    data_coletada = obter_data_coletada(driver)

    if data_coletada:
        print(f"Coleta ja estava baixada em: {data_coletada}")
        print("Clique no botao Coletar ignorado para evitar baixa duplicada.")
        return "ja_baixada", data_coletada

    print("Aguardando botao Coletar...")
    wait = WebDriverWait(driver, 20)
    try:
        botao_coletar = wait.until(EC.element_to_be_clickable((By.ID, "link_col")))
    except TimeoutException:
        raise Exception(
            "Botao Coletar nao apareceu na tela de detalhe. "
            f"URL atual: {driver.current_url}"
        )

    print("Botao Coletar encontrado. Clicando para baixar a coleta...")
    driver.execute_script("arguments[0].click();", botao_coletar)
    print(
        "Pausa de debug: aguardando "
        f"{PAUSA_APOS_CLIQUE_COLETAR}s para inspecionar possivel modal Sim/Nao..."
    )
    confirmou_modal = confirmar_modal_sim_se_existir(driver)
    if not confirmou_modal:
        time.sleep(PAUSA_APOS_CLIQUE_COLETAR)
    aceitar_alerta_se_existir(driver)

    print("Baixa acionada. Aguardando confirmacao da data coletada...")

    try:
        data_coletada = wait.until(lambda navegador: obter_data_coletada(navegador))
    except TimeoutException:
        raise Exception(
            "A baixa foi acionada, mas a data de 'Coletada' nao foi preenchida "
            "no tempo esperado."
        )

    print(f"Coleta baixada com sucesso em: {data_coletada}")
    print(
        "Pausa de debug: aguardando "
        f"{PAUSA_APOS_CONFIRMACAO_COLETA}s antes de sair da tela da coleta..."
    )
    time.sleep(PAUSA_APOS_CONFIRMACAO_COLETA)
    return "baixada", data_coletada


def testar_banco():
    print("=" * 60)
    print("Testando conexao e busca no banco")
    print("=" * 60)

    conn = conectar_banco()

    try:
        coletas = buscar_coletas_pendentes(conn)

        if not coletas:
            print("Conexao OK. Nenhum registro encontrado pelo select.")
            return

        print(f"Conexao OK. Coletas encontradas: {len(coletas)}")

        for coleta in coletas:
            print(
                f"Cod={coleta['Cod']} | "
                f"filial={coleta['filial']} | "
                f"Cte={coleta['Cte']} | "
                f"DataEmissao={coleta['DataEmissao']} | "
                f"data_alteracao={coleta['data_alteracao']}"
            )

    finally:
        conn.close()


def processar_coletas():
    print("=" * 60)
    print("Iniciando automacao SSW - baixa de coletas")
    print("=" * 60)

    conn = None
    driver = None

    try:
        if USAR_BANCO:
            conn = conectar_banco()
            coletas = buscar_coletas_pendentes(conn)
        else:
            coletas = COLETAS_TESTE

        if not coletas:
            print("Nenhum registro encontrado.")
            return

        filial = coletas[0]["filial"]
        print(f"Coletas encontradas: {len(coletas)}")
        print(f"Filial do lote: {filial}")

        driver = criar_driver()
        realizar_login(driver)
        janela_menu, janela_coleta = abrir_programa_003(driver, filial)

        for coleta in coletas:
            cod = coleta["Cod"]
            numero_coleta = coleta["Cte"]
            janela_detalhe = None

            if not numero_coleta:
                print(f"\nRegistro Cod {cod} ignorado: campo Cte vazio.")
                continue

            try:
                janela_detalhe = pesquisar_coleta(driver, janela_menu, janela_coleta, numero_coleta)
                status, data_coletada = baixar_coleta(driver)

                if USAR_BANCO:
                    marcar_coleta_baixada(conn, cod)

                    print(
                        f"Registro Cod {cod} marcado como coletado = '1' "
                        f"({status}, {data_coletada})."
                    )
                else:
                    print(
                        f"Modo teste: Registro Cod {cod} seria marcado como "
                        f"coletado = '1' ({status}, {data_coletada})."
                    )

            except Exception as exc:
                print(f"Erro ao processar Cod {cod}, coleta {numero_coleta}: {exc}")
                retirada_fila = False
                if USAR_BANCO:
                    try:
                        marcar_coleta_nao_baixada(conn, cod)
                        retirada_fila = True
                        print(f"Registro Cod {cod} marcado como coletado = '2' (nao baixada).")
                    except Exception as erro_marcacao:
                        print(f"Nao foi possivel retirar o Cod {cod} da fila automatica: {erro_marcacao}")
                enviar_alerta_email(cod, filial, numero_coleta, exc, retirada_fila)

            finally:
                fechar_janelas_detalhe(driver, janela_menu, janela_coleta)
                print(f"Aguardando {PAUSA_ENTRE_COLETAS}s antes da proxima coleta...")
                time.sleep(PAUSA_ENTRE_COLETAS)

        print("\nProcessamento finalizado.")

        fechar_janelas_ssw(driver)
        driver = None

        if DEBUG:
            print("\nModo DEBUG ativo.")
            print("Janelas do SSW fechadas apos o fim do lote.")

    finally:
        if conn is not None:
            conn.close()

        if driver is not None:
            if DEBUG:
                print("\nModo DEBUG ativado.")
                print("Chrome mantido aberto para inspecao.")
            else:
                driver.quit()


if __name__ == "__main__":
    try:
        if TESTAR_SOMENTE_BANCO:
            testar_banco()
        else:
            processar_coletas()
    except Exception as exc:
        print(f"Erro fatal na automacao: {exc}")
        enviar_alerta_fatal(exc)
        raise
