# -*- coding: utf-8 -*-
"""
COMPROVEI - EXPORTACAO DE CHAVES NFE VIA API

Substitui o fluxo Selenium por chamada REST ao relatorio de Danfes (WS614).
Fluxo:
- solicita CSV na API da Comprovei em blocos de ate 31 dias
- baixa o arquivo retornado pela URL
- extrai CSV quando vier compactado em ZIP
- consolida os CSVs em ultimo_arquivo.csv
- envia por FTP
- chama o importador PHP existente
"""

import csv
import os
import re
import time
import zipfile
import traceback
import unicodedata
import smtplib
from datetime import datetime, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from ftplib import FTP
from urllib.parse import urlparse

import requests

# =====================================================
# CONFIGURACOES

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
LOG_ARQUIVO = os.path.join(BASE_DIR, "comprovei_api_log.txt")
PASTA_DOWNLOADS = os.path.join(BASE_DIR, "downloads_api")
ARQUIVO_PADRAO = "ultimo_arquivo.csv"
CSV_FINAL = os.path.join(PASTA_DOWNLOADS, ARQUIVO_PADRAO)

DIAS_BUSCA = 85
MAX_DIAS_API = 31
REQUEST_TIMEOUT = 180

# =====================================================
# COMPROVEI WS614

URL_EXPORT_DANFES = "https://console-api.comprovei.com/exports/danfes"

# Use variaveis de ambiente no servidor, se preferir:
# COMPROVEI_USER=...
# COMPROVEI_PASS=...
COMPROVEI_USER = os.getenv("COMPROVEI_USER", "basfec-fassilog")
COMPROVEI_PASS = os.getenv("COMPROVEI_PASS", "Zz7tbfBiTdB5BulMEZycD5FSV0E8T0")

CAMPOS_CSV = [
    "Documento",
    "Emissão",
    "CNPJ Embarcador",
    "Embarcador",
    "Chave",
    "Status",
    "Data Últ. Ocorr.",
    "CNPJ Transp.",
    "Transportadora",
]

# =====================================================
# FTP

FTP_HOST = "ftp.ksoftlog.com.br"
FTP_PORT = 21
FTP_USER = "ksoftlogcom"
FTP_PASS = "#$!@2024_KS_TransportWeb"
FTP_REMOTE_PATH = "/comprovei/comprovei_csv/fassilog"

# =====================================================
# PHP IMPORTADOR

URL_PHP_IMPORTADOR = "https://moscassw.ksoftlog.com.br/send_ftp_ssw.php"

# =====================================================
# EMAIL

SMTP_HOST = "mail.ksoftlog.com.br"
SMTP_PORT = 587
SMTP_USER = "integracao@ksoftlog.com.br"
SMTP_PASS = "XR1KN792WR"
FROM_EMAIL = "integracao@ksoftlog.com.br"
TO_EMAIL = ["fabio@kreativesistemas.com.br"]


# =====================================================
# UTILIDADES

def log(msg):
    hora = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    linha = f"[{hora}] {msg}"
    print(linha)

    with open(LOG_ARQUIVO, "a", encoding="utf-8") as f:
        f.write(linha + "\n")


def medir_tempo(nome, inicio):
    tempo = round(time.time() - inicio, 2)
    log(f"{nome} concluido em {tempo}s")


def enviar_email_alerta(assunto, mensagem):
    try:
        msg = MIMEMultipart()
        msg["From"] = FROM_EMAIL
        msg["To"] = ", ".join(TO_EMAIL)
        msg["Subject"] = assunto

        corpo = f"""
ALERTA AUTOMACAO COMPROVEI API

Data/Hora: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}
Servidor/Script: comprovei_chavenfe_api.py

{mensagem}
"""
        msg.attach(MIMEText(corpo, "plain", "utf-8"))

        server = smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=60)
        server.starttls()
        server.login(SMTP_USER, SMTP_PASS)
        server.send_message(msg)
        server.quit()

        log("Email de alerta enviado.")
    except Exception as e:
        log(f"Falha ao enviar email de alerta: {e}")


def criar_pastas():
    os.makedirs(PASTA_DOWNLOADS, exist_ok=True)


def periodos_busca(dias_busca=DIAS_BUSCA, max_dias=MAX_DIAS_API):
    hoje = datetime.today().date()
    inicio_geral = hoje - timedelta(days=dias_busca - 1)
    inicio = inicio_geral

    while inicio <= hoje:
        fim = min(inicio + timedelta(days=max_dias - 1), hoje)
        yield inicio, fim
        inicio = fim + timedelta(days=1)


def response_body_json(resposta):
    dados = resposta.json()

    # Algumas respostas da Comprovei documentam "body" como objeto, outras
    # plataformas retornam body serializado. Aceitamos os dois formatos.
    body = dados.get("body", dados)
    if isinstance(body, str):
        try:
            import json
            body = json.loads(body)
        except Exception:
            body = {"user_message": body}

    return body


def solicitar_exportacao(data_inicial, data_final):
    payload = {
        "headers": {
            "username": COMPROVEI_USER,
            "password": COMPROVEI_PASS,
        },
        "body": {
            "formato_exportacao": "csv",
            "filtros": {
                "data_emissao_inicial": data_inicial.strftime("%Y-%m-%d"),
                "data_emissao_final": data_final.strftime("%Y-%m-%d"),
            },
            "campos_inclusos": CAMPOS_CSV,
        },
    }

    log(f"Solicitando WS614: {data_inicial} ate {data_final}")
    resposta = requests.post(
        URL_EXPORT_DANFES,
        json=payload,
        auth=(COMPROVEI_USER, COMPROVEI_PASS),
        timeout=REQUEST_TIMEOUT
    )

    if resposta.status_code >= 400:
        try:
            body_erro = response_body_json(resposta)
            mensagem = str(body_erro.get("message") or body_erro)
        except Exception:
            mensagem = resposta.text

        if "nao foram encontrados" in normalizar_header(mensagem).lower():
            log("Nenhum resultado encontrado neste periodo.")
            return None

        raise Exception(f"Erro WS614 HTTP {resposta.status_code}: {resposta.text}")

    body = response_body_json(resposta)
    url_download = body.get("user_message")

    if not url_download:
        mensagem = body.get("message") or body.get("internal_message") or str(body)
        if "nao foram encontrados" in normalizar_header(mensagem).lower():
            log("Nenhum resultado encontrado neste periodo.")
            return None
        raise Exception(f"WS614 nao retornou URL de download: {mensagem}")

    log("URL de download recebida.")
    return url_download


def baixar_arquivo(url_download, sufixo):
    resposta = requests.get(url_download, timeout=REQUEST_TIMEOUT)
    if resposta.status_code >= 400:
        raise Exception(f"Erro download HTTP {resposta.status_code}: {resposta.text[:500]}")

    caminho_url = urlparse(url_download).path
    nome = os.path.basename(caminho_url) or f"comprovei_{sufixo}.csv"

    if not nome.lower().endswith((".csv", ".zip")):
        content_type = resposta.headers.get("Content-Type", "")
        nome = f"comprovei_{sufixo}.zip" if "zip" in content_type else f"comprovei_{sufixo}.csv"

    destino = os.path.join(PASTA_DOWNLOADS, f"{sufixo}_{nome}")
    with open(destino, "wb") as f:
        f.write(resposta.content)

    log(f"Arquivo baixado: {destino}")
    return destino


def extrair_csv(caminho_arquivo):
    if not zipfile.is_zipfile(caminho_arquivo):
        return caminho_arquivo

    with zipfile.ZipFile(caminho_arquivo) as zf:
        csvs = [n for n in zf.namelist() if n.lower().endswith(".csv")]
        if not csvs:
            raise Exception(f"ZIP sem CSV: {caminho_arquivo}")

        nome_csv = csvs[0]
        destino = os.path.join(
            PASTA_DOWNLOADS,
            os.path.splitext(os.path.basename(caminho_arquivo))[0] + ".csv"
        )

        with zf.open(nome_csv) as origem, open(destino, "wb") as saida:
            saida.write(origem.read())

    log(f"CSV extraido: {destino}")
    return destino


def detectar_dialeto(amostra):
    try:
        return csv.Sniffer().sniff(amostra, delimiters=",;")
    except Exception:
        return csv.excel


def ler_csv(caminho_csv):
    with open(caminho_csv, "r", encoding="utf-8-sig", newline="") as f:
        amostra = f.read(4096)
        f.seek(0)
        dialeto = detectar_dialeto(amostra)
        leitor = csv.DictReader(f, dialect=dialeto)

        for row in leitor:
            yield {normalizar_header(k): (v or "").strip() for k, v in row.items() if k}


def normalizar_header(valor):
    texto = str(valor or "").strip()
    texto = texto.replace("Ã£", "a").replace("Ã§", "c").replace("Ãº", "u")
    texto = texto.replace("Ãš", "U").replace("Ãª", "e").replace("Ã©", "e")
    texto = unicodedata.normalize("NFKD", texto)
    texto = "".join(ch for ch in texto if not unicodedata.combining(ch))
    return " ".join(texto.split())


def escolher(row, *nomes):
    for nome in nomes:
        nome_norm = normalizar_header(nome)
        if nome_norm in row and row[nome_norm]:
            return row[nome_norm]
    return ""


def consolidar_csvs(csvs):
    vistos = set()
    chaves_enviadas = []
    total = 0

    with open(CSV_FINAL, "w", encoding="utf-8", newline="") as f:
        escritor = csv.DictWriter(
            f,
            fieldnames=["Documento", "CNPJ Embarcador", "Chave"],
            delimiter=",",
        )
        escritor.writeheader()

        for caminho_csv in csvs:
            for row in ler_csv(caminho_csv):
                documento = escolher(row, "Documento")
                cnpj = escolher(row, "CNPJ Embarcador")
                chave = escolher(row, "Chave")

                if not documento or not cnpj or not chave:
                    continue

                chave_limpa = "".join(ch for ch in chave if ch.isdigit())
                if len(chave_limpa) != 44:
                    continue

                chave_unica = (documento, cnpj, chave_limpa)
                if chave_unica in vistos:
                    continue

                vistos.add(chave_unica)
                escritor.writerow({
                    "Documento": documento,
                    "CNPJ Embarcador": cnpj,
                    "Chave": chave_limpa,
                })
                chaves_enviadas.append({
                    "documento": documento,
                    "cnpj": cnpj,
                    "chave": chave_limpa,
                })
                total += 1

    log(f"CSV consolidado: {CSV_FINAL} | linhas validas: {total}")
    return CSV_FINAL, total, chaves_enviadas


def enviar_ftp(arquivo):
    inicio = time.time()
    log("Conectando FTP...")

    ftp = FTP()
    ftp.connect(FTP_HOST, FTP_PORT, timeout=60)
    ftp.login(FTP_USER, FTP_PASS)
    ftp.cwd(FTP_REMOTE_PATH)

    with open(arquivo, "rb") as f:
        retorno = ftp.storbinary(f"STOR {os.path.basename(arquivo)}", f)

    ftp.quit()
    log(f"FTP retorno: {retorno}")
    medir_tempo("FTP", inicio)
    return retorno


def executar_php_online():
    inicio = time.time()
    log("Chamando PHP importador...")

    resposta = requests.get(URL_PHP_IMPORTADOR, timeout=REQUEST_TIMEOUT)
    log(f"Retorno PHP HTTP {resposta.status_code}: {resposta.text[:1000]}")

    if resposta.status_code >= 400:
        raise Exception(f"PHP importador retornou HTTP {resposta.status_code}")

    total_atualizados = extrair_total_atualizados(resposta.text)
    if total_atualizados is not None:
        log(f"PHP informou registros atualizados: {total_atualizados}")
    else:
        log("PHP nao informou quantidade de registros atualizados no retorno.")

    medir_tempo("PHP", inicio)
    return {
        "http_status": resposta.status_code,
        "total_atualizados": total_atualizados,
        "retorno": resposta.text[:1000],
    }


def extrair_total_atualizados(texto):
    padroes = [
        r"Total\s+atualizados?\s*:\s*(\d+)",
        r"ATUALIZADOS\s*:\s*(\d+)",
        r"Atualizados?\s*:\s*(\d+)",
    ]

    for padrao in padroes:
        match = re.search(padrao, texto, flags=re.IGNORECASE)
        if match:
            return int(match.group(1))

    return None


def executar():
    inicio_total = time.time()
    criar_pastas()

    csvs = []
    for indice, (inicio, fim) in enumerate(periodos_busca(), start=1):
        url = solicitar_exportacao(inicio, fim)
        if not url:
            continue

        baixado = baixar_arquivo(url, f"parte_{indice}")
        csvs.append(extrair_csv(baixado))

    if not csvs:
        raise Exception("Nenhum CSV foi gerado pela Comprovei nos periodos consultados.")

    arquivo_final, total, chaves_enviadas = consolidar_csvs(csvs)

    if total == 0:
        raise Exception("Nenhuma chave NFe valida encontrada no retorno da Comprovei.")

    retorno_ftp = enviar_ftp(arquivo_final)
    resultado_php = executar_php_online()

    log("========== RESUMO ==========")
    log(f"Chaves validas no CSV enviado: {total}")
    log(f"Arquivo enviado: {arquivo_final}")
    log(f"FTP confirmado: {retorno_ftp}")
    if resultado_php["total_atualizados"] is None:
        log("Registros atualizados no banco: nao informado pelo PHP importador")
    else:
        log(f"Registros atualizados no banco: {resultado_php['total_atualizados']}")

    log("Chaves enviadas para atualizacao:")
    for item in chaves_enviadas:
        log(
            "Documento: {documento} | CNPJ: {cnpj} | Chave: {chave}".format(
                **item
            )
        )

    medir_tempo("Processo total", inicio_total)


if __name__ == "__main__":
    log("========== INICIO COMPROVEI API ==========")

    try:
        executar()
        log("PROCESSO FINALIZADO COM SUCESSO")
    except Exception:
        erro = traceback.format_exc()
        log("ERRO GERAL:\n" + erro)
        enviar_email_alerta("ERRO COMPROVEI API - CHAVE NFE", erro)
        raise
