import time
from  generic.gn_request   import   Request # 
from  generic.gn_simple_api    import   simple_api #
from datetime import datetime

import json, re
import base64
import urllib3
from lxml import etree

from collections           import defaultdict

import xml.dom.minidom as minidom
import xml.etree.ElementTree as ET
from typing import Dict, List, Any, Union, Optional

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


class jlogwms(simple_api):
    
    def __init__(self, argv:list):
        self.response = None
        self.db = False
        self.output_count = 0
        self.suffix = "_app_pedidos3" 
        self.unparsed_data = {}
        self.setup(argv)
        
        self.rote_handler()

        # url = self.get_token_data("URL")
        
        self.jlogwms_request = Request(
                base_url = "https://apiwms.flsoft.com.br/grpims/wms/rest/TSM/"
            ,   params   = {}
            ,   data={}
            ,   verify= False
            ,   headers={
                'content-type': 'application/json; charset=utf-8'
            }
        )
         
        super(jlogwms, self, ).__init__(argv)
        
        
        print('Fim')
        
    
    # --------------------------------------------------
    # HANDLER DA ROTA: agrupa itens por pedido
    # --------------------------------------------------
    def post_route_handler_enviarOrdemSaida(self):
        """
        Lê o arquivo via txt_to_dict(), agrupa as linhas por NumOrdSaida
        e dispara um handle_send por pedido (com todos os itens aninhados).
        """
        # txt_to_dict agrupa por FGCUD — todas as linhas 'W' ficam aqui
        dados = self.txt_to_dict()
        linhas = dados.get('W', [])

        if not linhas:
            self.createLog("404", "Nenhuma linha 'W' encontrada no arquivo")
            return

        # --- Agrupa linhas por NumOrdSaida ---
        pedidos = defaultdict(list)
        for linha in linhas:
            num_ord = linha.get('NumOrdSaida', {}).get('valor')
            pedidos[num_ord].append(linha)

        # --- Processa um pedido por vez ---
        for num_ord_saida, itens in pedidos.items():

            # Usa a primeira linha para montar o cabeçalho do pedido
            primeira_linha     = itens[0]
            request_structure  = self.atualWs.get('request', [])

            # generate_values usa o template do map.json ("request")
            payload_cabecalho  = self.generate_values(request_structure, primeira_linha)

            # --- Monta ListaItemOrdemSaida com TODAS as linhas do pedido ---
            payload_cabecalho['ListaItemOrdemSaida'] = [
                {
                    # strip() porque DECIMAL vem com zeros: "000240.0000"
                    "CodProduto" : item.get('CodProduto', {}).get('valor', '').strip(),
                    "Qtde"       : item.get('Qtde',       {}).get('valor', 0)
                }
                for item in itens
            ]

            # Guarda a linha corrente para uso no handle_output
            self.unparsed_data = primeira_linha

            # Dispara o envio (chama send → throttling → api_request)
            self.handle_send(payload_cabecalho)


    # --------------------------------------------------
    # SAÍDA: grava resultado no arquivo cobol
    # --------------------------------------------------
    def handle_output_enviarOrdemSaida(self, data):
        """
        Trata o retorno do WMS:
        { "codigo": "1", "descricao": "OK"  }
        { "codigo": "99","descricao": "..." }
        """
        import json

        try:
            retorno     = json.loads(self.wms_jlog_request.req.text)
            codigo      = retorno.get('codigo', '99')
            descricao   = retorno.get('descricao', 'Erro desconhecido')
            num_ord     = self.unparsed_data.get('NumOrdSaida', {}).get('valor', '')

            flag_status = "OK" if codigo == "1" else "ER"
            estrutura   = f"{flag_status}|{num_ord}|{descricao}"

        except Exception as e:
            estrutura = f"ER|PARSE_ERROR|{str(e)}"

        # Primeiro write usa 'w+', os demais 'a+' (append)
        flag = "w+" if self.output_count == 0 else "a+"
        self.write_file(
            f"{estrutura}\n",
            self.saida_cobol,
            flag=flag,
            encoding='iso-8859-1'
        )
        self.output_count += 1

    def post_route_handler_enviarOrdemEntrada(self):
        """
        Processa o arquivo de entrada da OrdemEntrada.

        Tipos de registro (tipo_reg/valor):
            N → cabeçalho da nota  (1 linha)
            I → item / produto     (N linhas)
            L → lote do item anterior (N linhas por item)

        Relação I→L é POSICIONAL: cada linha L pertence ao I imediatamente anterior.
        """
        dados  = self.txt_to_dict()
        linhas = dados.get('W', [])

        if not linhas:
            self.createLog("404", "Nenhuma linha 'W' encontrada no arquivo")
            return

        # --- Separa cabeçalho, itens e lotes em uma única passagem ---
        linha_n       = None   # cabeçalho da nota
        itens         = []     # lista de dicts { linha_i, lotes: [] }
        item_atual    = None   # ponteiro para o item sendo construído

        for linha in linhas:
            tipo = linha.get('tipo_reg', {}).get('valor', '').strip()

            if tipo == 'N':
                # Cabeçalho da nota — só deve existir uma linha
                linha_n = linha

            elif tipo == 'I':
                # Novo item: cria entrada e avança o ponteiro
                item_atual = {
                    'linha' : linha,
                    'lotes' : []
                }
                itens.append(item_atual)

                # Se a própria linha I já traz lote preenchido,
                # captura como lote embutido (ex: AM03100 no exemplo)
                lote_inline = linha.get('Lote', {}).get('valor', '').strip()
                if lote_inline:
                    item_atual['lotes'].append(linha)

            elif tipo == 'L':
                # Lote pertence ao item anterior — relação posicional
                if item_atual is not None:
                    item_atual['lotes'].append(linha)
                else:
                    self.createLog("WARN", "Linha L sem item I precedente — ignorada")

        if not linha_n:
            self.createLog("404", "Linha de cabeçalho (tipo_reg=N) não encontrada")
            return

        # --- Monta cabeçalho via template do map.json ---
        request_structure = self.atualWs.get('request', [])
        payload           = self.generate_values(request_structure, linha_n)

        # --- Monta ListaItemOrdemEntrada com lotes aninhados ---
        payload['ListaItemOrdemEntrada'] = []

        for item in itens:
            li = item['linha']

            item_wms = {
                "CodProduto"           : li.get('CodProduto', {}).get('valor', '').strip(),
                "Qtde"                 : li.get('Qtde',       {}).get('valor', 0),
                "ListaLoteOrdemEntrada": []
            }

            for lote in item['lotes']:
                item_wms['ListaLoteOrdemEntrada'].append({
                    "Lote"           : lote.get('Lote',          {}).get('valor', '').strip(),
                    "DataValidade"   : self.format_datetime_wms(
                                        lote.get('DataValidade',  {}).get('valor', '')
                                    ),
                    "DataFabricacao" : self.format_datetime_wms(
                                        lote.get('DataFabricacao',{}).get('valor', '')
                                    ),
                    "Qtde"           : lote.get('QtdeLote',      {}).get('valor', 0)
                })

            payload['ListaItemOrdemEntrada'].append(item_wms)

        self.unparsed_data = linha_n
        self.handle_send(payload)


    # ------------------------------------------------------------------
    # Utilitário: converte "20260309 1154" → "09/03/2026" (formato WMS)
    # ------------------------------------------------------------------
    def format_datetime_wms(self, value, item=None):
        """
        Converte datetime do arquivo (YYYYMMDD HHmm ou YYYYMMDD HHmmss)
        para o formato esperado pelo WMS: DD/MM/YYYY
        Retorna None se valor vazio ou zerado.
        """
        try:
            val = str(value).strip().replace(' ', '')
            if not val or int(val) == 0:
                return None
            # Pega apenas a parte da data (primeiros 8 chars)
            data = val[:8]
            return f"{data[6:8]}/{data[4:6]}/{data[0:4]}"
        except:
            return None


    # ------------------------------------------------------------------
    # Saída: grava retorno no arquivo cobol
    # ------------------------------------------------------------------
    def handle_output_enviarOrdemEntrada(self, data):
        import json
        try:
            retorno   = json.loads(self.wms_jlog_request.req.text)
            codigo    = retorno.get('codigo',   '99')
            descricao = retorno.get('descricao','Erro desconhecido')
            num_ord   = self.unparsed_data.get('NumOrdEntrada', {}).get('valor', '')

            flag      = "OK" if codigo == "1" else "ER"
            estrutura = f"{flag}|{num_ord}|{descricao}"

        except Exception as e:
            estrutura = f"ER|PARSE_ERROR|{str(e)}"

        flag_file = "w+" if self.output_count == 0 else "a+"
        self.write_file(
            f"{estrutura}\n",
            self.saida_cobol,
            flag=flag_file,
            encoding='iso-8859-1'
        )
        self.output_count += 1

    def post_route_handler_enviarCancelamentoOrdemSaida(self):
        """
        Cancelamento de Ordem de Saída.
        Arquivo sempre tem uma única linha (tipo_reg=I).
        Estrutura plana — sem arrays aninhados.
        """
        dados  = self.txt_to_dict()
        linhas = dados.get('W', [])

        if not linhas:
            self.createLog("404", "Nenhuma linha 'W' encontrada no arquivo")
            return

        linha             = linhas[0]
        request_structure = self.atualWs.get('request', [])
        payload           = self.generate_values(request_structure, linha)

        self.unparsed_data = linha
        self.handle_send(payload)


    def handle_output_enviarCancelamentoOrdemSaida(self, data):
        import json
        try:
            retorno   = json.loads(self.wms_jlog_request.req.text)
            codigo    = retorno.get('codigo',    '99')
            descricao = retorno.get('descricao', 'Erro desconhecido')
            num_ord   = self.unparsed_data.get('NumOrdSaida', {}).get('valor', '')

            flag      = "OK" if codigo == "1" else "ER"
            estrutura = f"{flag}|{num_ord}|{descricao}"

        except Exception as e:
            estrutura = f"ER|PARSE_ERROR|{str(e)}"

        flag_file = "w+" if self.output_count == 0 else "a+"
        self.write_file(
            f"{estrutura}\n",
            self.saida_cobol,
            flag=flag_file,
            encoding='iso-8859-1'
        )
        self.output_count += 1

    # ------------------------------------------------------------------
    # Consulta status de Ordem de Saída
    # Chamada: GET consultarStatusOrdemSaida?;path->numOrdSaida=&NumNF=&SerieNF=
    # ------------------------------------------------------------------
    def format_rote_consultarStatusOrdemSaida(self, rote):
        """Injeta os path params na URL antes da chamada HTTP."""
        path     = self.rote_data.get('path', {}) if self.rote_data else {}
        num_ord  = path.get('numOrdSaida', '')
        num_nf   = path.get('NumNF', '')
        serie_nf = path.get('SerieNF', '')
        return f"consultarStatusOrdemSaida/{num_ord}/{num_nf}/{serie_nf}"

    def consultarStatusOrdemSaida_handler(self, data):
        """
        Trata retorno de /wms/rest/TSM/consultarStatusOrdemSaida/{numOrdSaida}/{NumNF}/{SerieNF}
        Saída sucesso : OK|{http_reason}|NumOrdSaida|NumPedido|Situacao|DescrSituacao
        Saída erro    : ER|{http_status reason}|
        """
        try:
            response = self.response

            if response and response.ok and isinstance(data, dict) and data:
                num_ord_saida  = data.get('NumOrdSaida',   '')
                num_pedido     = data.get('NumPedido',     '')
                situacao       = data.get('Situacao',      '')
                descr_situacao = data.get('DescrSituacao', '')
                desc           = response.reason or 'OK'
                estrutura      = f"OK|{desc}|{num_ord_saida}|{num_pedido}|{situacao}|{descr_situacao}"
            else:
                if response is not None:
                    desc = f"HTTP {response.status_code} {response.reason}"
                else:
                    desc = "Erro de conexao"
                estrutura = f"ER|{desc}|"

        except Exception as e:
            estrutura = f"ER|{str(e)}|"

        self.write_file(
            f"{estrutura}\n",
            self.saida_cobol,
            flag='w+',
            encoding='iso-8859-1'
        )

    def beforeSend_enviarEstoque(self, data):
        """
        Injeta DataEstoque (hoje) no payload antes do envio.
        O template não tem acesso a datetime, então fazemos aqui.
        """
        data['DataEstoque'] = datetime.now().strftime("%d/%m/%Y")
        return data


    def handle_output_enviarEstoque(self, data):
        """ Loga retorno do WMS por item enviado """
        try:
            retorno   = json.loads(self.jlogwms_request.req.text)
            codigo    = retorno.get('codigo',    '99')
            descricao = retorno.get('descricao', 'Erro desconhecido')
            nivel     = "200" if codigo == "1" else "500"
            self.createLog(nivel, f"enviarEstoque [{self.current_data.get('CodProduto')}]: {descricao}")
        except Exception as e:
            self.createLog("500", f"enviarEstoque PARSE_ERROR: {str(e)}")

    def post_route_handler_enviarFaturamentoOrdemSaida(self):
        """
        Faturamento de Ordem de Saída.
        Arquivo sempre tem uma única linha.
        Estrutura plana — sem arrays aninhados.
        """
        dados  = self.txt_to_dict()
        linhas = dados.get('W', [])

        if not linhas:
            self.createLog("404", "Nenhuma linha 'W' encontrada no arquivo")
            return

        linha             = linhas[0]
        request_structure = self.atualWs.get('request', [])
        payload           = self.generate_values(request_structure, linha)

        self.unparsed_data = linha
        self.handle_send(payload)


    def handle_output_enviarFaturamentoOrdemSaida(self, data):
        """
        Trata retorno do WMS:
        { "codigo": "1",  "descricao": "OK"  }
        { "codigo": "99", "descricao": "..." }
        """
        try:
            retorno   = json.loads(self.jlogwms_request.req.text)
            codigo    = retorno.get('codigo',    '99')
            descricao = retorno.get('descricao', 'Erro desconhecido')
            num_ord   = self.unparsed_data.get('NumOrdSaida', {}).get('valor', '')

            flag      = "OK" if codigo == "1" else "ER"
            estrutura = f"{flag}|{num_ord}|{descricao}"

        except Exception as e:
            estrutura = f"ER|PARSE_ERROR|{str(e)}"

        flag_file = "w+" if self.output_count == 0 else "a+"
        self.write_file(
            f"{estrutura}\n",
            self.saida_cobol,
            flag=flag_file,
            encoding='iso-8859-1'
        )
        self.output_count += 1

    def send(self, data):
        response = super().send(data)
        self.send_response = response
        if response is not None:
            self.call(f"response_code_{response.status_code}", response)
        return response

    def response_code_200(self, response):
        self.response_code_201(response)

    def prepare_send(self, data):
        request_method = self.method.upper()
        methods = self.atualWs.get('methods', ['POST'])
        if request_method in methods:
            self.current_method = request_method
        else:
            self.current_method = next(
                filter(lambda m: m not in ['DELETE'], methods), 'POST'
            )

        self.current_rote = self.atualWs['rote']
        if '{' in self.current_rote and '}' in self.current_rote:
            from generic.gn_simple_api import NoneAsEmptyFormatter
            fmt = NoneAsEmptyFormatter()
            self.current_rote = fmt.format(self.current_rote, **self.rote_data.get('path', {}))
            if self.current_rote.endswith('/'):
                self.current_rote = self.current_rote[:-1]

        func_rote = self.fixed_rote()
        self.current_rote = self.call(f"format_rote_{func_rote}", self.current_rote) or self.current_rote

    def format_rote_cadastrarRepresentante(self, rote):
        return 'cadastrarPessoa'

    def format_rote_cadastrarTransportador(self, rote):
        return 'cadastrarPessoa'

    def beforeSend_cadastrarProduto(self, data):
        if data['ListaDadosEmbalagem'][0]['ListaBarrasProd'][0]['CodBarras'] == '':
            del data['ListaDadosEmbalagem'][0]['ListaBarrasProd']

        # Só aceita POST
        if self.current_method == 'PUT':
            self.current_method = 'POST'

        return data
    
    def beforeSend_cadastrarPessoa(self, data):
        # Só aceita POST
        if self.current_method == 'PUT':
            self.current_method = 'POST'

        data['CodPropriet'] = int(f"{int(self.cdempresa)}0{int(self.cdfilial)}")

        return data
    
    def beforeSend_cadastrarRepresentante(self, data):
        # Só aceita POST
        if self.current_method == 'PUT':
            self.current_method = 'POST'

        data['CodPropriet'] = int(f"{int(self.cdempresa)}0{int(self.cdfilial)}")

        return data
    
    def beforeSend_cadastrarTransportador(self, data):
        # Só aceita POST
        if self.current_method == 'PUT':
            self.current_method = 'POST'

        data['CodPropriet'] = int(f"{int(self.cdempresa)}0{int(self.cdfilial)}")

        return data