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


from typing import Dict, Any
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 tempfile
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
import time


__VERSION__ = "20231207 Python %s" % (platform.python_version())


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 == "version":
            print("Yeboyebo AQ Extensión (%s)" % __VERSION__)

        else:
            msg = (
                "Tipo de ejecución desconocido: "
                + tipo
                + "\n\
            Posibles tipos: \n\
             - firma_pdf \n\
             - cliente_web\n\
             - cliente_web_certificado\n\
             - envio_mail\n\
             - sftp\n\
             - qrcode\n\
             - url_to_file\n\
             - version\n"
            )
            raise Exception(msg)

        if not ok:
            sys.exit(1)

    except Exception as e:
        sys.exit(e)


def cliente_web() -> bool:
    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"
        )

    with open(sys.argv[2], "r") as data_file:
        params = json.loads(data_file.read())

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

    result = llama_webservice(params, entrada)

    if "fsalida" in params:
        with open(params["fsalida"], "w", encoding="iso-8859-15") as fichero_salida:
            fichero_salida.write(json.dumps(result, ensure_ascii=False))

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

    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 = {}

    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:
                args[key] = params[key]

    result = getattr(requests, metodo.lower())(params["url"], **args)

    if return_response:
        return result

    if result.status_code != 200:
        raise Exception(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)

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

    elif metodo in ("GET", "POST", "PATCH", "DELETE"):
        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.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)

        s.sendmail(config.mailsender, mensaje.recipients, 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])

    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"):
                local_dir = sys.argv[7]
                remote_dir = sys.argv[8]
                if modo == "get_dir":
                    sftp.get_d(remote_dir, local_dir)  # remoto -> local

                else:
                    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):
    import uuid
    import tempfile

    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


if __name__ == "__main__":
    main()
