#!/usr/bin python3
# *-* coding: utf-8 *-*


from typing import Dict, Any, Optional
from cryptography.hazmat import backends
from cryptography.hazmat.primitives.serialization import (
    Encoding,
    PrivateFormat,
    NoEncryption,
)
from cryptography.hazmat.primitives.serialization import pkcs12

import sys
import json
import xmlrpc.client
import requests
import datetime
import os
import platform
import logging

import tempfile
import uuid
import gzip

# ENVIO_MAIL
import smtplib
import imaplib

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.image import MIMEImage
from email.utils import formatdate
import time

import base64
import fernet
import hashlib

from PIL import Image


__VERSION__ = "20250715 Python %s" % (platform.python_version())
_enable_debug:bool = False
_pid: int = os.getpid()
_main_conn = None


class ServidorMailClass(object):
    sqlserver = None
    sqlport = None
    sqluser = None
    sqlpassword = None
    sqldbname = None

    mailuser = None
    mailpassword = None
    mailserver = None
    starttls = False
    mailsender = None


class MensajeClass:
    def __init__(self):
        self.msgtexto = None
        self.recipients = []
        self.recipients_cc = []
        self.recipients_bcc = []
        self.codcliente = None
        self.asunto = None
        self.lista_recibos = []


def main():
    if len(sys.argv) == 1:
        sys.exit("No se han indicado parámetros")

    tipo = sys.argv[1]
    ok = True
    
    try:
        if tipo == "firma_pdf":
            ok = firma_pdf(*sys.argv[2:])
        elif tipo == "cliente_web":
            ok = cliente_web()
        elif tipo == "envio_mail":
            ok = envio_mail()
        elif tipo == "cliente_web_certificado":
            ok = cliente_web_certificado()
        elif tipo == "sftp":
            ok = conexion_sftp()
        elif tipo == "qrcode":
            ok = genera_qr()
        elif tipo == "url_to_file":
            ok = url_to_file()
        elif tipo == "md5":
            ok = resolve_md5()
        elif tipo == "scanner":
            import pyinsane2
            ok = scanner()
        elif tipo == "ofuscador":
            ok = ofuscar()
        elif tipo == "uuid":
            ok = uuid_gen()
        elif tipo == "version":
            print("Yeboyebo AQ Extensión (%s)" % __VERSION__)

        elif not tipo or tipo == "help":
            msg = (
                "Tipo de ejecución desconocido: "
                + tipo
                + "\n\
            Posibles tipos: \n\
             - cliente_web\n\
             - cliente_web_certificado\n\
             - envio_mail\n\
             - firma_pdf\n\
             - md5\n\
             - qrcode\n\
             - scanner\n\
             - sftp\n\
             - url_to_file\n\
             - uuid\n\
             - version\n\
            \n\n\
            Carpeta temporal : %s\n" % tempfile.gettempdir()
            )
            raise Exception(msg)

        if not ok:
            sys.exit(1)

    except Exception as e:
        sys.exit(e)

def uuid_gen():
    if len(sys.argv) < 3:
        raise Exception("uuid4: no ha indicado la acción")

    mode = sys.argv[2]
    action = sys.argv[3]


    if mode == "uuid4":
        if action == "generate":
            print(uuid.uuid4())
            return True

    elif mode == "check4":
        uuid_to_check = action
        version = uuid.UUID(uuid_to_check).version or 0
        print(version)
        return version == 4       

    print("uuid4: modo: %s acción %s desconocida. Disponibles:" % (mode))
    print("\t- uuid4 generate. Genera un UUID4")
    print("\t- check4 uuid. Comprueba si es un UUID4 válido")
    

def ofuscar():
    if len(sys.argv) < 3:
        raise Exception("ofuscar: no ha indicado la acción")
    
    if len(sys.argv) < 5:
        raise Exception("No se ha especificado el texto , o la clave")

    accion = sys.argv[2]
    key = base64.b64decode(sys.argv[3]).decode() #en base64 siempre
    texto = sys.argv[4]

    class_cifrado = Cifrado(key)

    if accion == "ofuscar":
        texto =base64.b64decode(texto).decode("utf-8")
        #print("ofuscar: %s, key: %s" % (texto, key))
        print(class_cifrado.ofuscar(texto))
        return True
    elif accion == "desofuscar":
        #print("desofuscar: %s, key:%s" % (texto, key))
        print(class_cifrado.resolver(texto))
        return True
    else:
        print("ofuscar: acción %s desconocida. Disponibles:" % accion)
        print("\t- ofuscar. Ofusca un texto")
        print("\t- desofuscar. Desofusca un texto ofuscado")
    
    return False

def ofuscar_texto():
    if len(sys.argv) < 4:
        raise Exception("ofuscar: no ha indicado el texto a ofuscar")

    texto = sys.argv[3]


    

def scanner():
    """
    scanner:
    scanner url source dpi mode output_file
    """

    if len(sys.argv) < 3:
        raise Exception("scanner: no ha indicado la acción")

    logger = logging.getLogger()
    sh = logging.StreamHandler()
    formatter= logging.Formatter(
            '%(levelname)-6s %(name)-30s %(message)s' 
        )
    sh.setFormatter(formatter)
    logger.setLevel(logging.ERROR)
    logger.addHandler(sh)
    
    accion = sys.argv[2]

    if accion == "list":
        return list_scanners()
    elif accion == "adquire":
        return adquire_scanner()
    elif accion == "sane_daemon":
        return scanner_sane_daemon()
    else:
        print("scanner: acción %s desconocida. Disponibles:" % accion)
        print("\t- list. Devuelve una lista con los escaners disponibles en el sistema")
        print("\t- adquire. Adquire un escaner y lo guarda en un archivo. url, source(default:flatbed), dpi(default:200), mode(default:bw), output_file(default:$TMPDIR/document.pdf)")

def scanner_sane_daemon():
    import pyinsane2.sane.daemon as sane_daemon
    sane_daemon.main_loop(sys.argv[3], sys.argv[4:6])
    return True
    

def list_scanners():
    pyinsane2.init()
    devices = pyinsane2.get_devices()
    for device in devices:
        print(device.name)
    pyinsane2.exit()
    return True


def adquire_scanner():
    if len(sys.argv) < 4:
        raise Exception("scanner: Debe indicar la ruta del scanner.")
    
    url = sys.argv[3]
    source = sys.argv[4] if len(sys.argv) > 4 else "flatbed" # flatbed, ADF
    dpi = sys.argv[5] if len(sys.argv) > 5 else 200 # 200
    mode = sys.argv[6] if len(sys.argv) > 6 else "Gray" # "Color", "Gray", "Lineart"
    output_file = sys.argv[7] if len(sys.argv) > 7 else os.path.join(tempfile.gettempdir(), "document.pdf") # default: /tmp/document.pdf

    pyinsane2.init()

    devices = pyinsane2.get_devices()
    device = None
    for device_ in devices:
        if device_.name == url:
            # print("Found scanner %s.Continue." % device_.name)
            device = device_
            break    
    
    if not device:
        raise Exception("No %s scanner found." % (url))
    
    pyinsane2.set_scanner_opt(device, 'source', [source])
    pyinsane2.set_scanner_opt(device, 'resolution', [dpi])
    pyinsane2.set_scanner_opt(device, 'mode', [mode])
    pyinsane2.maximize_scan_area(device)

    tmp_folder = tempfile.gettempdir()
    tmp_file_prefijo = os.path.join(tmp_folder, "scanner")
    result_ok = True
    try:
        print("Scanning ...")
        scan_session = device.scan(multiple=True)
        images_list = []
        while True:
            try:
                scan_session.scan.read()
            except EOFError:
                print("Got page %d." % (len(scan_session.images)))
                img = scan_session.images[-1]
                file_name = "%s_%d.jpeg" % (tmp_file_prefijo, len(scan_session.images))
                images_list.append(file_name)
                img.save(file_name, "jpeg")
    except StopIteration:
        
        if scan_session.images:
            print("%d pages found." % len(scan_session.images))
            img = scan_session.images[0]
            img.save(output_file, "PDF", resolution=100.0, save_all=True, append_images=images_list[1:])
            print("Saved to %s file." % output_file)
            print("Done")
        else:
            print("No pages") 
            result_ok = False
        

    pyinsane2.exit()
    return result_ok



def resolve_md5():
    if len(sys.argv) < 3:
        raise Exception(
            "resolve_md5: Debe indicar un segundo parámetro con el nombre del fichero"
        )

    file_name_or_value = sys.argv[2]
    if os.path.exists(file_name_or_value):
        with open(file_name_or_value, "rb") as f:
            value = hashlib.md5(f.read()).hexdigest()
    else:
        value = hashlib.md5(file_name_or_value.encode()).hexdigest()

    if len(sys.argv) == 4:
        with open(sys.argv[3], "w", encoding="iso-8859-15") as f:
            f.write(value)
    else:
        print(value)
    return True


def write_to_debug(text, forced = False):
    global _enable_debug, _pid

    if _enable_debug or forced:
        now = datetime.datetime.now()
        current_timestamp = now.strftime("%m/%d/%Y, %H:%M:%S.%f")
        text = "%s : %d - %s" % (current_timestamp, _pid, text)
        temp_file_name = os.path.join(tempfile.gettempdir(), "aqextension_debug.txt")
        try:
            with open(temp_file_name, "a") as f:
                f.write(text + "\n")
        except Exception as e:
            print("Error escribiendo en fichero de depuración %s: %s" % (temp_file_name, e))


def cliente_web() -> bool:
    global _enable_debug, _pid

    if len(sys.argv) != 3:
        raise Exception(
            "cliente_web: Debe indicar un segundo parámetro con el tipo de conexión y parámetros de llamada"
        )

    fichero_ordenes = sys.argv[2]
    result = None
    data: str = ""

    while True:
        _tiempo_ini = time.time()
        with open(fichero_ordenes, "r", encoding="ISO-8859-15") as data_file:
            params = json.loads(data_file.read(), strict=False)

        use_pipe = params["prefix_pipe"] if "prefix_pipe" in params else None

        if "enable_debug" in params:
            _enable_debug = params["enable_debug"]
            if _enable_debug:
                write_to_debug("DEBUG ACTIVADO")

        if "fentrada" in params:
            with open(params["fentrada"], "r") as fichero_entrada:
                entrada = fichero_entrada.read()
        else:
            entrada = ""

        result = llama_webservice(params, entrada)

        use_encode = (
            params["codificacion"] if "codificacion" in params else "ISO-8859-15"
        )

        if "fsalida" in params:
            tmp_file = os.path.join(tempfile.gettempdir(), str(uuid.uuid4()))
            
            if "only_key" in params:
                if not result or params["only_key"] not in result.keys():
                    raise Exception(
                        "%s no es una clave válida en el resultado: %s"
                        % (params["only_key"], result)
                    )

                data = result[params["only_key"]]
            else:
                try:
                    data = json.dumps(result, ensure_ascii=False)
                except Exception:
                    data = json.dumps(result.json())

            with open(tmp_file, "wb") as fichero_salida:
                fichero_salida.write(data.encode(use_encode, errors='replace'))

            os.rename(tmp_file, params["fsalida"])

        elif "codificacion" in params:
            salida_codificada = result
            try:
                print(str(salida_codificada).encode(use_encode))
            except Exception as e:
                print(str(salida_codificada).encode("UTF-8"))
        else:
            print(str(result))

        salir = params["close_when_finish"] if "close_when_finish" in params else True
        if salir:
            write_to_debug("Saliendo")
            break

        fichero_ordenes = None
        t_end = time.time() + 10
        _tiempo_fin = time.time()
        write_to_debug("Tiempo de espera TOTAL: %s" % (_tiempo_fin - _tiempo_ini))
        write_to_debug("Esperando nueva llamada use_pipe: %s" % (use_pipe))

        file_name_pipe = None
        if use_pipe:
            file_name_pipe = os.path.join(tempfile.gettempdir(), "%s" % (use_pipe))
            write_to_debug("Usando pipe %s" % file_name_pipe)

        while time.time() < t_end and not fichero_ordenes:
            try:
                texto_recibido = ""

                if use_pipe:

                    if os.path.exists(file_name_pipe) and os.path.getsize(file_name_pipe) > 0:
                        with open(file_name_pipe, "r") as f:
                            texto_recibido = f.read()
                        write_to_debug("He leido '%s' desde pipe %s" % (texto_recibido, file_name_pipe))
                        while True:
                            try:
                                if os.path.exists(file_name_pipe):
                                    write_to_debug("Borrando %s" % file_name_pipe)                            
                                    os.remove(file_name_pipe)
                                break
                            except Exception:
                                write_to_debug("Error borrando %s: %s" % (file_name_pipe, e))
                                time.sleep(0.01)
                else:
                    texto_recibido = sys.stdin.readline().strip()
                    if not texto_recibido:
                        continue   
                intentos = 0
                if texto_recibido:
                    write_to_debug("He leido argo %s" % texto_recibido)
                    while True:
                        intentos += 1
                        if use_pipe:
                            if (intentos > 10):
                                write_to_debug("Demasiados intentos. Saliendo")
                                break
                        try:
                            if os.path.exists(texto_recibido):
                                write_to_debug(
                                    "Recibido %s. Procesando..." % texto_recibido
                                )
                                fichero_ordenes = texto_recibido
                        except Exception as e:
                            write_to_debug("Error %s" % e)
                            time.sleep(0.01)
                            write_to_debug("Reintentando ...")
                            continue
                        break
                    write_to_debug("Termino de leer texto recibido")
            except Exception as e:
                write_to_debug("Error %s" % e)


        
        write_to_debug(
            "Tiempo de espera entre llamadas: %s" % (time.time() - _tiempo_fin)
        )

    return False if not result else True


def carga_entrada(params: Dict[str, Any], entrada):
    params["formato"] = (
        params["formato"].upper() if "formato" in params.keys() else "JSON"
    )
    return json.loads(entrada) if params["formato"] == "JSON" else entrada


def llama_xmlrpc(params, entrada):
    pEntrada = carga_entrada(params, entrada)
    url = params["url"]
    ws = params["ws"]
    lectorXmlRpc = xmlrpc.client.ServerProxy(url)
    web_service = lectorXmlRpc
    attrs = ws.split("/")

    for attr in attrs:
        web_service = getattr(web_service, attr)

    salida = web_service(pEntrada)
    return salida


def llama_test(params, entrada):
    pEntrada = carga_entrada(params, entrada)

    if params["formato"] == "JSON":
        pEntrada["resultado"] = "OK"
        salida = json.dumps(pEntrada)

    elif params["formato"] == "XML":
        salida = "<hola>%s</hola>" % pEntrada

    return salida


def llama_requests(
    params: Dict[str, Any], metodo: str, return_response=False, use_data_as_json=True
) -> Any:
    args = {}
    result_text = False
    tiempo1 = time.time()

    global _main_conn

    if "tipo_payload" in params.keys():
        use_data_as_json = params["tipo_payload"].upper() == "JSON"
        result_text = params["tipo_payload"].upper() != "JSON"
    if "return_response" in params.keys():
        return_response = params["return_response"]

    for key in ("params", "headers", "data", "cert", "verify"):
        if key in params.keys():
            if key == "data" and use_data_as_json:
                args[key] = json.dumps(params[key])
            else:
                if key == "headers" and not isinstance(
                    params[key], dict
                ):  # headers tiene que ser un diccionario.
                    headers = params[key].split(";")
                    args[key] = {}
                    for header in headers:
                        split_header = header.split("=")
                        key_header = split_header[0].strip()
                        args[key][key_header] = (
                            "" if len(split_header) == 1 else split_header[1]
                        )
                else:
                    args[key] = params[key]

    if not "headers" in args.keys():
        args["headers"] = {}
    #args["headers"]["Connection"] = "close"

    tiempo2 = time.time()
    write_to_debug("Tiempo carga argumentos: %s" % (tiempo2 - tiempo1))

    if not _main_conn:
        _main_conn = requests.Session()

        # lanzar consulta:
    result = getattr(_main_conn, metodo.lower())(params["url"], **args)
    tiempo3 = time.time()
    write_to_debug("Tiempo llamada: %s" % (tiempo3 - tiempo2))

    if return_response:
        return result

    if result.status_code != 200:
        raise Exception(result.text)

    else:
        if result_text:
            return result.text

    return result.json()


def llama_webservice(params, entrada):
    if "metodo" in params:
        metodo = params["metodo"].upper()
    else:
        metodo = "REST"

    if metodo == "XMLRPC":
        salida = llama_xmlrpc(params, entrada)

    elif metodo == "TEST":
        salida = llama_test(params, entrada)

    elif metodo in ("GET", "POST", "PATCH", "DELETE", "PUT"):
        salida = llama_requests(params, metodo)

    else:
        salida = "El método de llamada %s no está implementado" % metodo

    return salida


def firma_pdf(
    certificado,
    password,
    fichpdf,
    fichsignedpdf,
    logosign=False,
    coordenadas=[],
    paginafirma=0,
):
    try:
        from endesive import pdf

        date = datetime.datetime.utcnow() - datetime.timedelta(hours=2)
        date = date.strftime("D:%Y%m%d%H%M%S+00'00'")
        coordenadas = (
            (470, 0, 570, 100)
            if not coordenadas
            else tuple([int(i) for i in coordenadas.split("-")])
        )
        if logosign and not os.path.exists(logosign):
            raise Exception("No se encuentra %s" % logosign)

        dct = {
            "aligned": 0,
            "sigflags": 3,
            "sigpage": int(paginafirma),
            "auto_sigfield": True,
            # "sigandcertify": False,
            "signaturebox": coordenadas,
            "signform": False,
            "sigfield": "Signature",
            "signature_img_distort": False,  # default True
            "signature_img_centred": False,  # default True
            "contact": "mail@yeboyebo.es",
            "location": "Almansa",
            "signingdate": date.encode(),
            "reason": "Documento firmado digitalmente",
        }

        if not logosign:
            dct["signature"] = (
                "Documento firmado digitalmente"  # Si se especifica iamgen , no se muestra.
            )
        else:
            dct["signature_img"] = logosign

        with open(certificado, "rb") as fp:
            p12 = pkcs12.load_key_and_certificates(
                fp.read(), bytes(password, encoding="utf-8"), backends.default_backend()
            )

        with open(fichpdf, "rb") as fp:
            data_unsigned = fp.read()

        data_signed = pdf.cms.sign(data_unsigned, dct, p12[0], p12[1], p12[2], "sha256")

        with open(fichsignedpdf, "wb") as fp:
            fp.write(data_unsigned)
            fp.write(data_signed)

    except Exception as e:
        print("Error fima_pdf ", e)
        return False

    return True


def envio_mail() -> bool:
    try:
        mensaje = MensajeClass()

        file_name = (
            "correo.txt"
            if len(sys.argv) <= 12 or sys.argv[12] == "AAAAAA"
            else sys.argv[12]
        )  # Si no existe sys.argv[12] usamos correo.txt
        if os.path.exists(file_name):
            infile = open(file_name, "r", encoding="ISO-8859-15", errors="replace")
            mensaje.asunto = infile.readline()
            mensaje.msgtexto = infile.read()
            infile.close()
        else:
            raise Exception("No existe el fichero %s" % (file_name))

        mensaje.recipients = sys.argv[2].split(",")

        if len(sys.argv) > 15 and sys.argv[15] != "AAAAAA":
            mensaje.recipients_cc = sys.argv[15].split(",")

        if len(sys.argv) > 16 and sys.argv[16] != "AAAAAA":
            mensaje.recipients_bcc = sys.argv[16].split(",")

        config = ServidorMailClass()
        config.mailuser = sys.argv[3]
        config.mailpassword = sys.argv[4]
        config.mailserver = sys.argv[5] + ":" + sys.argv[6]
        config.starttls = (
            resolve_bool(sys.argv[17])
            if len(sys.argv) > 17 and sys.argv[17] != "AAAAAA"
            else True
        )
        config.mailsender = sys.argv[7]
        outer = MIMEMultipart()
        outer["From"] = config.mailsender
        outer["To"] = ", ".join(mensaje.recipients)
        outer["Cc"] = ", ".join(mensaje.recipients_cc) if mensaje.recipients_cc else ""
        outer["BCC"] = (
            ", ".join(mensaje.recipients_bcc) if mensaje.recipients_bcc else ""
        )
        outer["Subject"] = mensaje.asunto
        
        outer["Date"] = formatdate(localtime=True)

        outer.preamble = "You will not see this in a MIME-aware mail reader.\n"
        # outer.add_header("Content-Type", "text/html")
        anchoImagenFirma = sys.argv[10]
        altoImagenFirma = sys.argv[11]

        outer.attach(
            MIMEText(
                mensaje.msgtexto,
                "html" if mensaje.msgtexto.strip().startswith("<html>") else "plain",
                "utf-8",
            )
        )

        if anchoImagenFirma != "AAAAAA" and altoImagenFirma != "AAAAAA":
            img_firma = (
                '<img src="cid:image" width ="'
                + anchoImagenFirma
                + '" height = "'
                + altoImagenFirma
                + '">'
            )
        else:
            img_firma = '<img src="cid:image">'

        outer.attach(MIMEText(img_firma, "html", "utf-8"))

        f = sys.argv[8]
        if f != "AAAAAA":
            f = f.replace("AAAAAA", " ")
            ff = f.split("|||")
            for fi in ff:
                try:
                    with open(fi, "rb") as fil:
                        part = MIMEApplication(fil.read(), Name=os.path.basename(fi))
                        part["Content-Disposition"] = (
                            'attachment; filename="%s"' % os.path.basename(fi)
                        )
                        outer.attach(part)
                except IOError:
                    print("Error al adjuntar el fichero." + fi)
                    return False

        imagenfirma = sys.argv[9]
        if imagenfirma != "AAAAAA":
            imagenfirma = imagenfirma.replace("AAAAAA", " ")
            # añdimos la imagen
            fp = open(imagenfirma, "rb")
            part3 = MIMEImage(fp.read())
            fp.close()

            part3.add_header("Content-ID", "<image>")
            outer.attach(part3)

        # Now send or store the message
        composed = outer.as_string()
        s = smtplib.SMTP(config.mailserver)
        # with smtplib.SMTP(config.mailserver) as s:
        if config.starttls:
            s.starttls()

        if config.mailuser and config.mailpassword:
            s.login(config.mailuser, config.mailpassword)

        lista_emails = mensaje.recipients + mensaje.recipients_cc + mensaje.recipients_bcc
        s.sendmail(config.mailsender, lista_emails, composed)
        s.quit()

        imap_port = (
            None if len(sys.argv) < 14 or sys.argv[13] == "AAAAAA" else sys.argv[13]
        )
        imap_url = (
            None if len(sys.argv) < 15 or sys.argv[14] == "AAAAAA" else sys.argv[14]
        )

        if imap_port and config.mailuser and config.mailpassword:
            try:
                imap = imaplib.IMAP4_SSL(imap_url, imap_port)
                imap.login(config.mailuser, config.mailpassword)
                imap.append(
                    "Sent",
                    "\\Seen",
                    imaplib.Time2Internaldate(time.time()),
                    composed.encode("utf8"),
                )
                imap.logout()
            except Exception as error:
                print(
                    "ERROR - Error creando copia en %s:%s.Sent: %s"
                    % (imap_url, imap_port, str(error))
                )

        return True
    except smtplib.SMTPConnectError:
        print("ERROR - No se puede conectar al servidor SMTP.")
        return False
    except smtplib.SMTPAuthenticationError:
        print("ERROR - Error de autenticación SMTP.")
        return False
    except smtplib.SMTPSenderRefused:
        print("ERROR - Dirección del remitente rechazada.")
        return False
    except smtplib.SMTPRecipientsRefused:
        print("ERROR - Todas las direcciones de destinatarios se rechazaron.")
        return False
    except smtplib.SMTPServerDisconnected:
        print("ERROR - El servidor se desconecta inesperadamente.")
        return False
    except smtplib.socket.gaierror:
        print(
            "ERROR - Servidor SMTP no encontrado.Verifique el nombre de host de su servidor SMTP."
        )
        return False
    except Exception as e:
        print("Error sending mail ", e)
        return False


def cliente_web_certificado() -> bool:
    tipo = sys.argv[2]

    headers = {}
    body_data = ""

    try:
        if tipo == "batuz":
            if len(sys.argv) != 9:
                print("Invalid arguments size")
                return False

            json_file = os.path.abspath(sys.argv[7])
            if not os.path.exists(json_file):
                print("File not found", json_file)
                return False

            with open(json_file) as file_data:
                json_data = json.load(file_data)

            body_file = os.path.abspath(sys.argv[6])
            if not os.path.exists(body_file):
                print("File not found", body_file)
                return False

            file_ = open(body_file, "r", encoding="UTF-8")
            body_data = gzip.compress(file_.read().encode(), 4)
            file_.close()

            headers = {
                "Accept-Encoding": "gzip",
                "Content-Encoding": "gzip",
                "Content-Type": "application/octet-stream",
                "eus-bizkaia-n3-version": "1.0",
                "eus-bizkaia-n3-content-type": "application/xml",
                "eus-bizkaia-n3-data": json.dumps(json_data).encode(),
                "Content-Length": str(len(body_data)),
            }

        else:
            print("Unknown client", tipo)
            return False

        pem_file = pfx_to_pem(sys.argv[4], sys.argv[5])
        url = sys.argv[3]

        data = {
            "url": url,
            "data": body_data,
            "headers": headers,
            "cert": pem_file,
            "verify": True,
        }
        response = llama_requests(data, "POST", True, False)

        result = str(response.headers) if tipo == "batuz" else response.text
        result_file = os.path.abspath(sys.argv[8])

        file_header = open(result_file, "wb")
        file_header.write(result.encode())
        file_header.close()

        if tipo == "batuz":
            file_result_xml = open("%s.response_content.xml" % result_file, "wb")
            file_result_xml.write(response.content or b"")
            file_result_xml.close()

    except Exception as error:
        print("Error cliente_web_certificado", error)
        return False

    return True


# https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068#gistcomment-3743732
def pfx_to_pem(pfx_path, pfx_pass):
    """Decrypts the .pfx file to be used with requests."""

    t_pem = tempfile.NamedTemporaryFile(suffix=".pem", delete=False)
    pem_name = t_pem.name
    f_pem = open(pem_name, "wb")
    pfx = open(pfx_path, "rb").read()

    private_key, main_cert, add_certs = pkcs12.load_key_and_certificates(
        pfx, pfx_pass.encode(), backends.default_backend()
    )
    f_pem.write(
        private_key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
    )
    f_pem.write(main_cert.public_bytes(Encoding.PEM))

    for ca in add_certs:
        f_pem.write(ca.public_bytes(Encoding.PEM))
    f_pem.close()

    return pem_name


def conexion_sftp() -> bool:
    """Establece una conexión sftp."""

    import pysftp

    username = sys.argv[2]
    password = sys.argv[3]
    hostname = sys.argv[4]
    modo = sys.argv[5]
    puerto = int(sys.argv[6])
    debug = resolve_bool(sys.argv[7] if len(sys.argv) > 7 else "")

    try:
        cnopts = pysftp.CnOpts()
        cnopts.hostkeys = None
        with pysftp.Connection(
            host=hostname,
            username=username,
            password=password,
            port=puerto,
            cnopts=cnopts,
        ) as sftp:
            if modo in ("get_dir", "put_dir"):
                if debug:
                    print("conexion_sftp. mode: %s" % (modo))
                local_dir = sys.argv[7]
                remote_dir = sys.argv[8]
                if modo == "get_dir":
                    sftp.get_d(remote_dir, local_dir)  # remoto -> local
                else:
                    # Check if local_dir is a file
                    if os.path.isfile(local_dir):
                        if debug:
                            print("conexion_sftp. local_dir is a file")
                            print("conexion_sftp. change remote_dir: %s" % (remote_dir))
                        sftp.chdir(remote_dir)
                        if debug:
                            print("conexion_sftp. sending file: %s" % (local_dir))
                        sftp.put(local_dir)
                    else:
                        if debug:
                            print("conexion_sftp. local_dir is a directory")
                        sftp.put_d(local_dir, remote_dir)

                    sftp.cwd(remote_dir)
                    directory_structure = sftp.listdir_attr()  # local -> remoto
                    for attr in directory_structure:
                        print(attr.filename, attr)  # print de propiedades

        return True

    except Exception as error:
        print("Error sftp", error)
        return False

    return True


def resolve_bool(value):
    return str(value).lower() in ["true", "1", "s"]


def genera_qr() -> bool:
    """Genera qrcode y lo guarda como un fichero."""

    import qrcode

    ruta_img = sys.argv[2]
    qr_string = sys.argv[3].replace("þ", " ")
    received_params = json.loads(sys.argv[4]) if len(sys.argv) > 4 else {}

    # received_params opcional "version":9,"error_correction":"ERROR_CORRECT_M","box_size":2,"border":2}'

    params = {
        "version": 9,
        "error_correction": qrcode.constants.ERROR_CORRECT_M,
        "box_size": 2,
        "border": 2,
    }

    for key, value in received_params.items():
        if key not in params.keys():
            print("Error qrcode:El argumento %s no es válido" % (key))
            return False

        if key == "error_correction":
            value = getattr(qrcode.constants, value)
        params[key] = value

    try:
        qr = qrcode.QRCode(**params)
        qr.add_data(qr_string)
        qr.make(fit=True)
        img = qr.make_image(fill_color="black", back_color="white")
        img.save(ruta_img)
    except Exception as error:
        print("Error qrcode:" + error)
        return False

    return True


def url_to_file() -> bool:
    """Imprime una url en una impresora especificada."""

    if len(sys.argv) < 3:
        print("Error url_to_file: No se ha especificado url")
        return False

    url = sys.argv[2]
    folder = sys.argv[3] if len(sys.argv) > 3 else None
    file_name = _url_to_file(url, folder)

    if file_name:
        print(file_name)
        return True

    return False


def _url_to_file(url, folder=None):

    list_url = url.split("/")

    file_name = os.path.join(
        folder if folder else tempfile.gettempdir(),
        "%s_%s" % (str(uuid.uuid4()), list_url[-1]),
    )

    result = requests.get(url)

    if result.status_code != 200:
        print("ERROR: La url %s no es válida" % (url))
        return False

    file_ = open(file_name, "wb")
    file_.write(result.content)
    file_.close()

    return file_name

class Cifrado:

    def __init__(self, key: str = None):
        """Init."""

        self.fernet : "fernet.Fernet" = None
        self.key: str = key


    def ofuscar(self, texto: str) -> bool:
        """Save password."""

        # save_password("password","CIF_EMPRESA")
        self.init_fernet()
        if len(texto) < 64:
            texto = texto.ljust(64, "\n")
            
        encoded_bytes = self.fernet.encrypt(texto.encode())
        return base64.b64encode(encoded_bytes).decode()

        

    def resolver(self, cipher_text: str) -> str:
        """Resuelve password."""

        self.init_fernet()

        cipher_bytes = base64.b64decode(cipher_text.encode())
        text = self.fernet.decrypt(cipher_bytes).decode()
        text = text.rstrip("\n")
        return text
    
    def init_fernet(self) -> None:
        """Initialize fernet."""

        if not self.fernet:
            if not self.key:
                raise Exception("No se ha especificado key")
            key = self.key
            # salt = os.urandom(9)
            salt = b'\xa1\x8c\x8f\xdb\x8fi?_\xde'
            key_encoded = key.encode()

            hmac = hashlib.pbkdf2_hmac("sha256", key_encoded , salt, 10000)
            dict_passwd = {
                # Algorithm:
                # .. pbkdf2: short algorithm name
                # .. sha256: hash function used
                # .. 4: number of zeroes used on iterations
                "algorithm": "pbkdf2-sha256-4",
                "salt": base64.b64encode(salt).decode(),
                "hash": base64.b64encode(hmac).decode(),
            }
            hashed_password = "%(algorithm)s:%(salt)s:%(hash)s" % dict_passwd

            key_salt = hashlib.sha256(hashed_password.encode()).digest()
            new_key = hashlib.pbkdf2_hmac("sha256", key_encoded, key_salt, 10000)
            key64 = base64.urlsafe_b64encode(new_key)
            self.fernet = fernet.Fernet(key64)

if __name__ == "__main__":
    main()
