#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.

"Módulo Trazabilidad de Productos Veterinarios SENASA Resolución 369/2013"

# Información adicional y documentación:
# http://www.sistemasagiles.com.ar/trac/wiki/TrazabilidadProductosVeterinarios

__author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2014 Mariano Reingart"
__license__ = "GPL 3.0+"
__version__ = "1.11d"

# http://senasa.servicios.pami.org.ar/

import os
import socket
import sys
import datetime, time
import traceback
import py3simplesoap.client
from py3simplesoap.client import SoapClient, SoapFault, parse_proxy, \
                                set_http_wrapper
from py3simplesoap.simplexml import SimpleXMLElement
from cStringIO import StringIO

# importo funciones compartidas:
from .utils import leer, escribir, leer_dbf, guardar_dbf, N, A, I, json, \
                  dar_nombre_campo_dbf, get_install_dir, BaseWS, \
                  inicializar_y_capturar_excepciones

HOMO = True
TYPELIB = False

WSDL = "https://servicios.pami.org.ar/trazaenvet.WebService?wsdl"
LOCATION = "https://servicios.pami.org.ar/trazaenvet.WebService?wsdl"

# Formato de TransaccionSenasaDTO (SaveTransaccion)
TRANSACCION_DTO = [
    ('gln_origen', 13, A),
    ('gln_destino', 13, A),
    ('f_operacion', 10, A),
    ('f_elaboracion', 10, A),
    ('f_vto', 10, A),
    ('id_evento', 15, N),
    ('cod_producto', 14, A),
    ('n_cantidad', 30, N),
    ('n_serie', 20, A),
    ('n_lote', 50, A),
    ('n_cai', 15, A),
    ('n_cae', 15, A),
    ('id_motivo_destruccion', 5, A),
    ('n_manifiesto', 15, N),
    ('en_transporte', 1, A), # boolean
    ('n_remito', 15, A),
    ('motivo_devolucion', 100, A),
    ('observaciones', 1000, A),
    ('n_vale_compra', 15, A),
    ('apellidoNombres', 255, A),
    ('direccion', 200, A),
    ('numero', 6, N),
    ('localidad', 15, A),
    ('provincia', 15, A),
    ('n_postal', 8, A),
    ('cuit', 11, A),
    ('codigo_transaccion', 14, A),
]

# Formato para TransaccionSenasa (getTransacciones)
TRANSACCIONES = [
    ('id_transaccion_global', 15, N),
    ('id_transaccion', 15, N),
    ('f_transaccion', 10, A),
    ('f_operacion', 10, A),
    ('f_vencimiento', 10, A),
    ('f_elaboracion', 10, A),
    ('d_evento', 100, A),
    ('n_cantidad', 30, N),
    ('id_unidad', 15, N),
    ('d_unidad', 100, A),
    ('cod_producto', 14, A),
    ('id_unidad', 15, N),
    ('n_serie', 20, A),
    ('n_lote', 50, A),
    ('n_cai', 15, A),
    ('n_cae', 15, A),
    ('d_motivo_destruccion', 50, A),
    ('d_manifiesto', 15, A),
    ('en_transporte', 1, A),
    ('n_remito', 30, A),
    ('motivo_devolucion', 200, A),
    ('observaciones', 1000, A),
    ('n_vale_compra', 15, A),
    ('apellidoNombres', 255, A),
    ('direccion', 200, A),
    ('numero', 6, A),
    ('localidad', 250, A),
    ('provincia', 250, A),
    ('n_postal', 8, A),
    ('cuit', 11, A),
    ('d_agente_informador', 255, A),
    ('d_agente_origen', 255, A),
    ('d_agente_destino', 255, A),
    ('d_producto', 250, A),
    ('d_estado_transaccion', 30, A),
    ('d_tipo_transaccion', 30, A),
]

# Formato para Errores
ERRORES = [
    ('_c_error', 4, A),                 # código
    ('_d_error', 250, A),               # descripción
    ]


class TrazaVet(BaseWS):
    "Interfaz para el WebService de Trazabilidad de Veterinarios SENASA"

    _public_methods_ = ['SaveTransaccion', 'SendCancelaTransac',
                        'SendConfirmaTransacc', 'SendAlertaTransacc',
                        'GetTransacciones',
                        'Conectar', 'LeerError', 'LeerTransaccion',
                        'SetUsername',
                        'SetParametro', 'GetParametro',
                        'GetCodigoTransaccion', 'GetResultado', 'LoadTestXML']

    _public_attrs_ = [
        'Username', 'Password',
        'CodigoTransaccion', 'Errores', 'Resultado',
        'XmlRequest', 'XmlResponse',
        'Version', 'InstallDir',
        'Traceback', 'Excepcion',
        'CantPaginas', 'HayError', 'TransaccionSenasa',
        ]

    _reg_progid_ = "TrazaVet"
    _reg_clsid_ = "{2E28D79D-23B0-4C1E-85B9-64BDC41B8FCF}"

    # Variables globales para BaseWS:
    HOMO = HOMO
    WSDL = WSDL
    Version = "%s %s %s" % (__version__, HOMO and 'Homologación' or '',
                            py3simplesoap.client.__version__)

    def __init__(self, reintentos=1):
        self.Username = self.Password = None
        self.TransaccionSenasa = []
        BaseWS.__init__(self, reintentos)

    def inicializar(self):
        BaseWS.inicializar(self)
        self.CodigoTransaccion = self.Errores = self.Resultado = None
        self.Resultado = ''
        self.Errores = []   # lista de strings para la interfaz
        self.errores = []   # lista de diccionarios (uso interno)
        self.CantPaginas = self.HayError = None

    def __analizar_errores(self, ret):
        "Comprueba y extrae errores si existen en la respuesta XML"
        self.errores = ret.get('errores', [])
        self.Errores = ["%s: %s" % (it['c_error'], it['d_error'])
                        for it in ret.get('errores', [])]
        self.Resultado = ret.get('resultado')

    def Conectar(self, cache=None, wsdl=None, proxy="", wrapper=None, cacert=None, timeout=30):
        # Conecto usando el método estandard:
        ok = BaseWS.Conectar(self, cache, wsdl, proxy, wrapper, cacert, timeout,
                              soap_server="jetty")
        if ok:
            # si el archivo es local, asumo que ya esta corregido:
            if not self.wsdl.startswith("file"):
                # corrijo ubicación del servidor (localhost:59050 en el WSDL)
                location = self.wsdl[:-5]
                ws = self.client.services['IWebServiceSenasa']
                ws['ports']['IWebServiceSenasaPort']['location'] = location

            # Establecer credenciales de seguridad:
            self.client['wsse:Security'] = {
                'wsse:UsernameToken': {
                    'wsse:Username': self.Username,
                    'wsse:Password': self.Password,
                    }
                }
        return ok

    @inicializar_y_capturar_excepciones
    def SaveTransaccion(self, usuario, password,
                        gln_origen=None, gln_destino=None,
                        f_operacion=None, f_elaboracion=None, f_vto=None,
                        id_evento=None, cod_producto=None, n_cantidad=None,
                        n_serie=None, n_lote=None, n_cai=None, n_cae=None,
                        id_motivo_destruccion=None, n_manifiesto=None,
                        en_transporte=None, n_remito=None,
                        motivo_devolucion=None, observaciones=None,
                        n_vale_compra=None, apellidoNombres=None,
                        direccion=None, numero=None, localidad=None,
                        provincia=None, n_postal=None, cuit=None
                         ):
        "Realiza el registro de una transacción de productos fitosanitarios. "
        # creo los parámetros para esta llamada
        params = {  'gln_origen': gln_origen,
                    'gln_destino': gln_destino,
                    'f_operacion': f_operacion,
                    'f_elaboracion': f_elaboracion,
                    'f_vto': f_vto,
                    'id_evento': id_evento,
                    'cod_producto': cod_producto,
                    'n_cantidad': n_cantidad,
                    'n_serie': n_serie,
                    'n_lote': n_lote,
                    'n_cai': n_cai or None,
                    'n_cae': n_cae or None,
                    'id_motivo_destruccion': id_motivo_destruccion or None,
                    'n_manifiesto': n_manifiesto or None,
                    'en_transporte': en_transporte or None,
                    'n_remito': n_remito or None,
                    'motivo_devolucion': motivo_devolucion or None,
                    'observaciones': observaciones or None,
                    'n_vale_compra': n_vale_compra or None,
                    'apellidoNombres': apellidoNombres or None,
                    'direccion': direccion or None,
                    'numero': numero or None,
                    'localidad': localidad or None,
                    'provincia': provincia or None,
                    'n_postal': n_postal or None,
                    'cuit': cuit or None,
                    }
        res = self.client.saveTransacciones(
            arg0=params,
            arg1=usuario,
            arg2=password,
        )
        ret = res['return']
        self.CodigoTransaccion = ret.get('codigoTransaccion')
        self.__analizar_errores(ret)
        return True

    @inicializar_y_capturar_excepciones
    def SendCancelaTransac(self, usuario, password, codigo_transaccion):
        " Realiza la cancelación de una transacción"
        res = self.client.sendCancelaTransac(
            arg0=codigo_transaccion,
            arg1=usuario,
            arg2=password,
        )
        ret = res['return']
        self.CodigoTransaccion = ret.get('codigoTransaccion')
        self.__analizar_errores(ret)
        return True

    @inicializar_y_capturar_excepciones
    def SendConfirmaTransacc(self, usuario, password, p_ids_transac, f_operacion, n_cantidad=None):
        "Confirma la recepción de un medicamento"
        res = self.client.sendConfirmaTransacc(
            arg0=usuario,
            arg1=password,
            arg2={'p_ids_transac': p_ids_transac, 'f_operacion': f_operacion,
                  'n_cantidad': n_cantidad,
                 },
        )
        ret = res['return']
        self.CodigoTransaccion = ret.get('codigoTransaccion')
        self.__analizar_errores(ret)
        return True

    @inicializar_y_capturar_excepciones
    def SendAlertaTransacc(self, usuario, password, p_ids_transac_ws):
        "Alerta un medicamento, acción contraria a “confirmar la transacción”."
        res = self.client.sendAlertaTransacc(
            arg0=usuario,
            arg1=password,
            arg2=p_ids_transac_ws,
        )
        ret = res['return']
        self.CodigoTransaccion = ret.get('id_transac_asociada')
        self.__analizar_errores(ret)
        return True

    @inicializar_y_capturar_excepciones
    def GetTransacciones(self, usuario, password,
                id_transaccion=None, id_evento=None, gln_origen=None,
                fecha_desde_t=None, fecha_hasta_t=None,
                fecha_desde_v=None, fecha_hasta_v=None,
                gln_informador=None, id_tipo_transaccion=None,
                gtin_elemento=None, n_lote=None, n_serie=None,
                n_remito_factura=None,
                ):
        "Trae un listado de las transacciones que no están confirmadas"

        # preparo los parametros de entrada opcionales:
        kwargs = {}
        if id_transaccion is not None:
            kwargs['arg2'] = id_transaccion
        if id_evento is not None:
            kwargs['arg3'] = id_evento
        if gln_origen is not None:
            kwargs['arg4'] = gln_origen
        if fecha_desde_t is not None:
            kwargs['arg5'] = fecha_desde_t
        if fecha_hasta_t is not None:
            kwargs['arg6'] = fecha_hasta_t
        if fecha_desde_v is not None:
            kwargs['arg7'] = fecha_desde_v
        if fecha_hasta_v is not None:
            kwargs['arg8'] = fecha_hasta_v
        if gln_informador is not None:
            kwargs['arg9'] = gln_informador
        if id_tipo_transaccion is not None:
            kwargs['arg10'] = id_tipo_transaccion
        if gtin_elemento is not None:
            kwargs['arg11'] = gtin_elemento
        if n_lote is not None:
            kwargs['arg12'] = n_lote
        if n_serie is not None:
            kwargs['arg13'] = n_serie
        if n_remito_factura is not None:
            kwargs['arg14'] = n_remito_factura

        # llamo al webservice
        res = self.client.getTransacciones(
            arg0=usuario,
            arg1=password,
            **kwargs
        )
        ret = res['return']
        if ret:
            self.__analizar_errores(ret)
            self.CantPaginas = ret.get('cantPaginas')
            self.HayError = ret.get('hay_error')
            self.TransaccionSenasa = [it for it in ret.get('list', [])]
        return True

    def  LeerTransaccion(self):
        "Recorro TransaccionSenasa devuelto por GetTransacciones"
         # usar GetParametro para consultar el valor retornado por el webservice

        if self.TransaccionSenasa:
            # extraigo el primer item
            self.params_out = self.TransaccionSenasa.pop(0)
            return True
        else:
            # limpio los parámetros
            self.params_out = {}
            return False

    def LeerError(self):
        "Recorro los errores devueltos y devuelvo el primero si existe"

        if self.Errores:
            # extraigo el primer item
            er = self.Errores.pop(0)
            return er
        else:
            return ""

    def SetUsername(self, username):
        "Establezco el nombre de usuario"
        self.Username = username

    def SetPassword(self, password):
        "Establezco la contraseña"
        self.Password = password

    def GetCodigoTransaccion(self):
        "Devuelvo el código de transacción"
        return self.CodigoTransaccion

    def GetResultado(self):
        "Devuelvo el resultado"
        return self.Resultado


def main():
    "Función principal de pruebas (obtener CAE)"
    import os, time, sys
    global WSDL, LOCATION

    DEBUG = '--debug' in sys.argv

    ws = TrazaVet()

    ws.Username = 'testwservice'
    ws.Password = 'testwservicepsw'

    if '--prod' in sys.argv and not HOMO:
        WSDL = "https://servicios.pami.org.ar/trazavet.WebService?wsdl"
        print("Usando WSDL:", WSDL)
        sys.argv.pop(sys.argv.index("--prod"))

    if '--proxy' in sys.argv and not HOMO:
        proxy = sys.argv.pop(sys.argv.index("--proxy") + 1)
        print("Usando proxy:", proxy)
        sys.argv.pop(sys.argv.index("--proxy"))
    else:
        proxy = None

    # Inicializo las variables y estructuras para el archivo de intercambio:
    transaccion_dto = []
    transacciones = []
    errores = []
    formatos = [('TransaccionDTO', TRANSACCION_DTO, transaccion_dto),
                ('Transacciones', TRANSACCIONES, transacciones),
                ('Errores', ERRORES, errores),
               ]

    if '--formato' in sys.argv:
        print("Formato:")
        for msg, formato, lista in formatos:
            comienzo = 1
            print("=== %s ===" % msg)
            print("|| %-25s || %-12s || %-5s || %-4s || %-10s ||" % (
                "Nombre", "Tipo", "Long.", "Pos(txt)", "Campo(dbf)"))
            claves = []
            for fmt in formato:
                clave, longitud, tipo = fmt[0:3]
                clave_dbf = dar_nombre_campo_dbf(clave, claves)
                claves.append(clave_dbf)
                print("|| %-25s || %-12s || %5d ||   %4d   || %-10s ||" % (
                    clave, tipo, longitud, comienzo, clave_dbf))
                comienzo += longitud
        sys.exit(0)

    if '--cargar' in sys.argv:
        if '--dbf' in sys.argv:
            leer_dbf(formatos[:1], {})
        elif '--json' in sys.argv:
            for formato in formatos[:1]:
                archivo = open(formato[0].lower() + ".json", "r")
                d = json.load(archivo)
                formato[2].extend(d)
                archivo.close()
        else:
            for formato in formatos[:1]:
                archivo = open(formato[0].lower() + ".txt", "r")
                for linea in archivo:
                    d = leer(linea, formato[1])
                    formato[2].append(d)
                archivo.close()

    ws.Conectar("", WSDL, proxy)

    if ws.Excepcion:
        print(ws.Excepcion)
        print(ws.Traceback)
        sys.exit(-1)

    # Datos de pruebas:

    if '--test' in sys.argv:
        transaccion_dto.append(dict(
            gln_origen="9876543210982", gln_destino="3692581473693",
            f_operacion=datetime.datetime.now().strftime("%d/%m/%Y"),
            f_elaboracion=datetime.datetime.now().strftime("%d/%m/%Y"),
            f_vto=(datetime.datetime.now()+datetime.timedelta(30)).strftime("%d/%m/%Y"),
            id_evento=11,
            cod_producto="88900000000001",
            n_cantidad=1,
            n_serie=int(time.time()*10),
            n_lote=datetime.datetime.now().strftime("%Y"),
            n_cai="123456789012345",
            n_cae="",
            id_motivo_destruccion=0,
            n_manifiesto="",
            en_transporte="N",
            n_remito="1234",
            motivo_devolucion="",
            observaciones="prueba",
            n_vale_compra="",
            apellidoNombres="Juan Peres",
            direccion="Saraza", numero="1234",
            localidad="Hurlingham", provincia="Buenos Aires",
            n_postal="1688",
            cuit="20267565393",
            codigo_transaccion=None,
        ))

    # Opciones principales:

    if '--confirma' in sys.argv:
        if '--loadxml' in sys.argv:
            ws.LoadTestXML("trazamed_confirma.xml")  # cargo respuesta
            ok = ws.SendConfirmaTransacc(usuario="pruebasws", password="pruebasws",
                                   p_ids_transac="1", f_operacion="31-12-2013")
            if not ok:
                raise RuntimeError(ws.Excepcion)
        ws.SendConfirmaTransacc(*sys.argv[sys.argv.index("--confirma")+1:])
    elif '--alerta' in sys.argv:
        ws.SendAlertaTransacc(*sys.argv[sys.argv.index("--alerta")+1:])
    elif '--cancela' in sys.argv:
        ws.SendCancelaTransac(*sys.argv[sys.argv.index("--cancela")+1:])
    elif '--consulta' in sys.argv:
        ws.GetTransacciones(
                            *sys.argv[sys.argv.index("--consulta")+1:]
                            )
        print("CantPaginas", ws.CantPaginas)
        print("HayError", ws.HayError)
        # print("TransaccionSenasa", ws.TransaccionSenasa)
        # parametros comunes de salida (columnas de la tabla):
        claves = [k for k, v, l in TRANSACCIONES]
        # extiendo la lista de resultado para el archivo de intercambio:
        transacciones.extend(ws.TransaccionSenasa)
        # encabezado de la tabla:
        print("||", "||".join(["%s" % clave for clave in claves]), "||")
        # recorro los datos devueltos (TransaccionSenasa):
        while ws.LeerTransaccion():
            for clave in claves:
                print("||", ws.GetParametro(clave))         # imprimo cada fila
            print("||")
    else:
        argv = [argv for argv in sys.argv if not argv.startswith("--")]
        if not transaccion_dto:
            if len(argv)>10:
                ws.SaveTransaccion(*argv[1:])
            else:
                print("ERROR: no se indicaron todos los parámetros requeridos")
        elif transaccion_dto:
            try:
                usuario, password = argv[-2:]
            except:
                print("ADVERTENCIA: no se indico parámetros usuario y passoword")
                usuario, password = "senasaws", "Clave2013"
            for i, dto in enumerate(transaccion_dto):
                print("Procesando registro", i)
                del dto['codigo_transaccion']
                ws.SaveTransaccion(usuario, password, **dto)
                dto['codigo_transaccion'] = ws.CodigoTransaccion
                errores.extend(ws.errores)
                print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
                    ws.Resultado,
                    ws.CodigoTransaccion,
                    '|'.join(ws.Errores or []),
                    ))
        else:
            print("ERROR: no se especificaron productos a informar")

    if not transaccion_dto:
        print("|Resultado %5s|CodigoTransaccion %10s|Errores|%s|" % (
                ws.Resultado,
                ws.CodigoTransaccion,
                '|'.join(ws.Errores or []),
                ))

    if ws.Excepcion:
        print(ws.Traceback)

    if '--grabar' in sys.argv:
        if '--dbf' in sys.argv:
            guardar_dbf(formatos, True, {})
        elif '--json' in sys.argv:
            for formato in formatos:
                archivo = open(formato[0].lower() + ".json", "w")
                json.dump(formato[2], archivo, sort_keys=True, indent=4)
                archivo.close()
        else:
            for formato in formatos:
                archivo = open(formato[0].lower() + ".txt", "w")
                for it in formato[2]:
                    archivo.write(escribir(it, formato[1]))
            archivo.close()


# busco el directorio de instalación (global para que no cambie si usan otra dll)
INSTALL_DIR = TrazaVet.InstallDir = get_install_dir()


if __name__ == '__main__':

    # ajusto el encoding por defecto (si se redirije la salida)
    if not hasattr(sys.stdout, "encoding") or sys.stdout.encoding is None:
        import codecs, locale
        sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout,"replace");
        sys.stderr = codecs.getwriter(locale.getpreferredencoding())(sys.stderr,"replace");

    if '--register' in sys.argv or '--unregister' in sys.argv:
        import pythoncom
        import win32com.server.register
        win32com.server.register.UseCommandLine(TrazaVet)
    elif "/Automate" in sys.argv:
        # MS seems to like /automate to run the class factories.
        import win32com.server.localserver
        #win32com.server.localserver.main()
        # start the server.
        win32com.server.localserver.serve([TrazaVet._reg_clsid_])
    else:
        main()
