import base64
from   generic.gn_request   import   Request # 
from   woocommerce.woocommerce       import   woocommerce # 
from  datetime              import datetime, timedelta
import traceback, json

class forseti(woocommerce):
    def __init__(self, argv:list):
        self.total_variations = {}

        super(forseti, self, ).__init__(argv)

    """ 
    Metodo para manipular a nota fiscal
    """

    def beforeSend_orders_pedido_nfe_xml(self, data):
        """
        Upload do XML da NF-e em orders/{id}/nfe-xml.

        Foge do fluxo padrão (JSON): POST/PUT mandam o XML em multipart/form-data
        no campo `xml`; DELETE vai sem corpo. A chamada é feita aqui e
        response_code_* é disparado manualmente pra preservar o ciclo de
        relacionamento/MD5 do framework.
        """
        import os

        method  = (self.current_method or 'POST').upper()
        pedido  = self.current_data.get('pedido')
        caminho_arquivo = self.current_data.get('caminho_arquivo')
        nro_doc = self.current_data.get('softdib_id')

        # Pula o self.send() padrão em qualquer caminho daqui pra frente
        self.ignore_request = True

        if not pedido:
            self.createLog(400, f"NF {nro_doc}: pedido sem relacionamento WooCommerce — XML nao enviado")
            return

        request_kwargs = {
            "rote"   : f"orders/{pedido}/nfe-xml",
            "method" : method,
        }

        if method in ("POST", "PUT"):
            caminho = caminho_arquivo

            if not caminho or not os.path.isfile(caminho):
                self.createLog(404, f"NF {nro_doc}: XML inexistente — {caminho!r}")
                return

            with open(caminho, 'rb') as fh:
                request_kwargs['files'] = {
                    'xml': (os.path.basename(caminho), fh.read(), 'application/xml')
                }

        try:
            self.send_response = self.woocommerce_request.doRequest(**request_kwargs)
        except Exception as e:
            self.createLog(500, f"NF {nro_doc}: erro no {method} — {e}")
            return

        status = self.send_response.status_code or 0

        # Em POST/PUT, só fecha o relacionamento (response_code_*) se a
        # meta_data também atualizou — assim o TBRELACIONAID nao fica
        # gravado com a meta_data desatualizada na WooCommerce.
        if method in ("POST", "PUT") and 200 <= status < 300:
            if not self._atualizar_meta_data_nfe(pedido, self.current_data, nro_doc):
                self.createLog(500, f"NF {nro_doc}: meta_data falhou — relacionamento NAO gravado, NF sera reenviada na proxima execucao")
                return

        self.call(f"response_code_{status}", self.send_response)

    def _atualizar_meta_data_nfe(self, pedido, data, nro_doc):
        from datetime import date, datetime

        def _to_str(v):
            if isinstance(v, (date, datetime)):
                return v.strftime('%Y-%m-%d')
            return v if v is None else str(v)

        meta_map = [
            ("_forseti_nfe_number", _to_str(data.get('numero_nf'))),
            ("_forseti_nfe_series", _to_str(data.get('serie_nf'))),
            ("_forseti_nfe_key",    _to_str(data.get('chave_nf'))),
            ("_forseti_nfe_date",   _to_str(data.get('data_nf'))),
        ]
        meta_payload = [{"key": k, "value": v} for k, v in meta_map if v not in (None, '', 'None')]

        # Sem nada pra atualizar = nao bloqueia o relacionamento
        if not meta_payload:
            return True

        try:
            resp = self.woocommerce_request.doRequest(
                rote   = f"orders/{pedido}",
                method = "PUT",
                json   = {
                    "meta_data": meta_payload,
                    "status": "completed"
                },
            )
        except Exception as e:
            self.createLog(500, f"NF {nro_doc}: erro ao atualizar meta_data do pedido {pedido} — {e}")
            return False

        if not (200 <= (resp.status_code or 0) < 300):
            self.createLog(resp.status_code, f"NF {nro_doc}: falha ao atualizar meta_data do pedido {pedido} — {resp.text[:200]}")
            return False

        return True
    
    """
    Metodo para manipular os dados antes de enviar
    """
    def beforeSend_products(self, data) :

        #-----------------------------------------
        # Categorias
        #-----------------------------------------

        category_ids = []

        category = self.select("SELECT IDAPI FROM TBRELACIONAID WHERE NMTABELA='TBGRUPO' AND IDSOFTDIB = '{}' AND NMNOMEAPI = '{}'".format(f"{self.current_data['grupo']}.{self.current_data['subgrupo']}", self.apiName))
        
        if category:
            category_ids.append({
                "id": category[0].get('IDAPI')
            })

        data['categories'] = category_ids

        #------------------------------------------
        
        
        # -----------------------------------------
        # Se o produto tiver estoque menor ou igual a 0, o estoque é zerado
        # -----------------------------------------

        if (float(data['stock_quantity']) <= 0):
            data['stock_quantity'] = 0

        # -----------------------------------------

        # -----------------------------------------
        # Se a requisição for do tipo POST, o status do produto é definido como privado, para evitar que o produto seja publicado antes de ser totalmente integrado.
        # -----------------------------------------

        if self.current_rote == "POST":
            data['status'] = 'private'

        # -----------------------------------------
            
        # -----------------------------------------
        # Rotina Forseti para verificar se o produto é do tipo variável ou simples, 
        # caso seja variável ele irá enviar a requisição para a rota de variações.
        # -----------------------------------------

        parent_id = data.get('parent_id', None)
        is_parent = False
        
        check_total_in_db = self.select(f"SELECT COUNT(*) AS total FROM TBPRODUTO WHERE TBPRODUTO.CDPRODUTO = '{parent_id}'")
        check_total_variations = self.select(f"SELECT COUNT(*) AS total FROM TBPRODUTO WHERE TBPRODUTO.CDPRODUTO LIKE '{parent_id}-%'")

        if check_total_in_db[0]['total'] == 1 and check_total_variations[0]['total'] > 0:
            is_parent = True

        # ------------------------------------------

        if is_parent:

            # ------------------------------------------
            # Produto do tipo variável
            # ------------------------------------------

            data['type'] = 'variable'

            options = self.select(f"SELECT REPLACE(TBPRODUTO.DSPRODUTO,'\"',\"'\") as description FROM TBPRODUTO WHERE TBPRODUTO.CDPRODUTO LIKE '{parent_id}-%'")

            data['attributes'] = [
                {
                    "name": "Variação",
                    "position": 0,
                    "visible": True,
                    "variation": True,
                    "options": []
                }
            ]

            for option in options:
                data['attributes'][0]['options'].append(option['description'])

            # ------------------------------------------

        # ------------------------------------------
        # Remove o parent_id do data, pois ele não é necessário para o produto do tipo
        # ------------------------------------------
        
        del data['parent_id']

        # ------------------------------------------

        if not is_parent:

            # ------------------------------------------
            # Produto do tipo simples
            # ------------------------------------------

            parent_id_split = parent_id.split('-')
            parent_id = parent_id_split[:-1]
            parent_id_code = '-'.join(parent_id)

            validando_banco = self.select(f"SELECT TBPRODUTO.CDPRODUTO FROM TBPRODUTO WHERE TBPRODUTO.CDPRODUTO = '{parent_id_code}'")

            if validando_banco:
                relatement = self.select(f"SELECT TBRELACIONAID.IDAPI FROM TBRELACIONAID WHERE NMTABELA='TBPRODUTO' AND NMNOMEAPI = 'woocommerce' AND TBRELACIONAID.IDSOFTDIB = '{parent_id_code}'")

                if relatement:
                    data = {
                        "regular_price": str(data['regular_price']),
                        "sku": data['sku'],
                        "manage_stock": True,
                        "stock_quantity": data['stock_quantity'],
                        "stock_status": "instock",
                        "weight": str(data['weight']),
                    }
                    # -------------------------------------------

                # --------------------------------

        if self.current_data['woocommerce_id'] and ":" in self.current_data['woocommerce_id']:
            ids = self.current_data['woocommerce_id'].split(':')
            parent_id = ids[0]
            variation_id = ids[1]

            self.current_rote = f"products/{parent_id}/variations/{variation_id}"

        if self.current_method == "PUT":
            data = {
                "regular_price": str(data['regular_price']),
                "manage_stock": True,
                "stock_quantity": data['stock_quantity'],
                "stock_status": "instock",
                "meta_data": [
                    {
                        "key": "_forseti_prazo_envio", 
                        "value": "{}".format(self.current_data.get('prazo_envio', "0")) 
                    }
                ]
            }

        return data
    
    def send_handler(self, data) :
        """
            Metodo para manipular o envio
        """
        
        # o data pode ser alterado aqui dentro
        self.prepare_send(data)
        
        # Faz um tratamento no dados antes de enviar
        func_rote = self.fixed_rote()
        func = f"beforeSend_{func_rote}"#.format(func_rote)
        # Tratamento de dados antes de enviar
        beforeSend = self.call(func, data, is_required= self.atualWs.get('need_beforeSend',False))

        if beforeSend:
            data = beforeSend

        #if self.hasAttr(func, self) :
        #    data = getattr(self, func)( data )
        #print("stop")
        if hasattr(self, 'ignore_request') and self.ignore_request == True:
            self.ignore_request = False
            return
        
        self.send( data )
        
        # Executa apoas enviar - cada rota tem a sua especifica - deve-se criar dentro da arquivo da api
        
        if not self.atualWs['send'] or self.send_response.status_code in range(200,300) :
            func = f"afterSend_{func_rote}"#format(func_rote)
            # Tratamento de dados depois de enviar
            #if self.hasAttr(func, self) :
            #    getattr(self, func)( data )
            self.call(func, data )
            
        self.current_data = {}

    def envios_forseti(self, data, props): 
        """
            Metodo para manipular o envio de produtos, categorias e variações
        """

        envios = {
            'FRENET_03298': "T00013", # PAC
            'FRENET_03220': "T00027", # Sedex
            'FRENET_TOT_EXPRSS': "T00019", # Total Express 2 dias úteis
            'FRENET_MDN': 'T00026', # Movvi
            'FRENET_BRX': 'T00149' # BRIX
        }

        if data.get('method_id') == 'free_shipping':
            return "T00013"  # Frete grátis
        
        if data.get('method_id') == 'pickup_location':
            return "T00020"  # Frete grátis
        
        if data.get('method_title') == 'MERCADO LIVRE':
            return "T00134" # Mercado Livre
        
        if data.get('method_title') == 'SHOPEE':
            return "T00065" # Mercado Livre

        frenet_value = next(
            (item["value"] for item in data['meta_data'] if item["key"] == "FRENET_ID"),
            None  # Valor padrão caso não encontre
        )
        
        return envios.get(frenet_value, 'T00001')
    
    def forma_envio_frete_forseti(self, value, props):
        """
            Metodo para manipular o envio de pedidos
        """
        
        return "9" if value == "pickup_location" else "1"
    
    def origem_forseti(self, value, props):
        try:
            metadata = self.current_order.get('meta_data', [])

            key_to_find = '_forseti_marketplace_name'

            marketplace_name = next((item for item in metadata if item.get('key') == key_to_find), None)

            origem_map = {
                "MERCADO LIVRE": "193",
                "SHOPEE": "194"
            }

            if marketplace_name:
                marketplace_name_value = marketplace_name.get('value', '')
                return origem_map.get(marketplace_name_value, '203')
            else:
                return '203'
        except:
            return '203'
        
        
        return origem_map.get(self.cliente_origem, '203')
    
    def pagamentos_forseti(self, data, props):
        import re 

        """
            Metodo para manipular o envio de pedidos
        """

        text_search = ''

        try:
            metadata = self.current_order.get('meta_data', [])
            key_to_find = '__ASAAS_ORDER'
            second_key_to_find = '_forseti_marketplace_name'

            asaas_order = next((item for item in metadata if item.get('key') == key_to_find), None)
            marketplace_name = next((item for item in metadata if item.get('key') == second_key_to_find), None)

            if marketplace_name:
                marketplace_conds = {
                    'MERCADO LIVRE': '224',
                    'SHOPEE': '291'
                }

                cdcond = marketplace_conds.get(marketplace_name.get('value'), '')
                
                return cdcond


            order_pay = json.loads(asaas_order['value']) if asaas_order else None

            if order_pay.get('billingType') == 'CREDIT_CARD':
                description = order_pay.get('description', '')
                match = re.search(r'Parcela \d+ de (\d+)', description)
                installment_count = int(match.group(1)) if match else 1

                if installment_count > 1:
                    text_search = f"ASAAS - CARTAO DE CREDITO {installment_count}X" 
                    
                else:
                    text_search = 'ASAAS - CARTAO DE CREDITO'

                cdpg = self.select(f"SELECT * FROM `TBCONDPGTO` WHERE `DSCONDPGTO` LIKE '%{text_search}%'")

                if cdpg:
                    return cdpg[0]['CDCONDPGTO']
                
            if order_pay.get('billingType') == 'BOLETO':
                text_search = 'ASAAS - BOLETO'
                cdpg = self.select(f"SELECT * FROM `TBCONDPGTO` WHERE `DSCONDPGTO` LIKE '%{text_search}%'")

                if cdpg:
                    return cdpg[0]['CDCONDPGTO']
                
            if order_pay.get('billingType') == 'PIX':
                text_search = 'ASAAS - PIX'
                cdpg = self.select(f"SELECT * FROM `TBCONDPGTO` WHERE `DSCONDPGTO` LIKE '%{text_search}%'")

                if cdpg:
                    return cdpg[0]['CDCONDPGTO']
            
        except Exception as e:
            print('Erro ao processar o método de pagamento:')
            traceback.print_exc()

        return '999'
    
    def observacao_forseti(self, data, props):
        observacoes = [data]

        for produto in self.current_order.get('line_items', []):
            meta_data = produto.get('meta_data', [])
            valores = [] 

            if not meta_data:
                continue

            chaves = ['Comprimento', 'Servico', 'Detalhes', '_reduced_stock', 'Observações']

            chaves_label = {
                '_reduced_stock': 'Corte',
                'Comprimento': 'Comprimento',
                'Servico': 'Servico',
                'Detalhes': 'Detalhes',
                'Observações': 'Observações'
            }

            for chave in chaves:
                item = next((m for m in meta_data if m.get('key') == chave), None)

                if item and item.get('value'):
                    chave_label = chaves_label.get(chave)

                    try:
                        from decimal import Decimal

                        if chave == '_reduced_stock':
                            item['value'] = f"{Decimal(item['value']) / Decimal(100):.3f}"
                    except:
                        pass

                    valores.append(f"{chave_label}: {item['value']}")

            if ",".join(valores) == "":
                continue
            
            observacoes.append("{}:|{}|Quantidade: {}|===============================|".format(produto.get("sku"), "|".join(valores), produto.get("quantity")))
            
        return '|'.join(observacoes)
    
    def previsao_entrega_pedido(self, data):
        item = next((m for m in self.current_order['meta_data'] if m.get('key') == '_forseti_prazo_max'), None)

        if item:
            return item.get('value')

        return 2

    def get_order_items(self, order):
        items = order.get('line_items', [])
        
        try:
            for index, item in enumerate(items):
                search_field = '_custom_mm_length'
                meta_data = item.get('meta_data', [])

                comprimento = next((m for m in meta_data if m.get('key') == search_field), None)

                if comprimento:
                    total_quantity = item.get('quantity', 0)
                    total_comprimento = int(comprimento.get('value', 0))
                    
                    total_quantidade = total_quantity * total_comprimento
                    
                    items[index]['quantity'] = total_quantidade/1000
        except:
            pass

        return [ self.extractValues(item, "itenscarrinho_fields") for item in items ]

    def response_code_404(self, response):
        self.createLog(404, "{} -> Produto {} -> Produto não encontrado no WooCommerce: {}".format(self.current_rote, self.current_data['softdib_id'], response.text))
        return super().response_code_404(response)

    def response_code_400(self, req):
        try:
            error_res = req.json()
            if 'code' in error_res and error_res['code'] == 'product_invalid_sku':
                if 'message' in error_res and 'SKU inválido ou duplicado.' in error_res['message']:
                    
                    data = json.loads(req.request.body)

                    search_product = self.woocommerce_request.doRequest(rote=f"products?sku={data['sku']}")

                    if search_product.ok:

                        product_data = search_product.json()

                        if len(product_data) > 0:
                            product_data = product_data[0]

                            productSoftdib = self.select(f"SELECT * FROM TBPRODUTO WHERE CDEMPRESA IN ({int(self.cdempresa)}, 0) AND FGSITUACAO = '0' AND CDFILIAL IN ({int(self.cdfilial)}, 0) AND CDPRODUTO = '{product_data['sku']}'")
                            checkExistInRelatement = self.select(f"SELECT * FROM TBRELACIONAID WHERE NMNOMEAPI = 'woocommerce' AND NMTABELA = 'TBPRODUTO' AND IDSOFTDIB = '{product_data['sku']}' AND IDAPI = {product_data['id']}")

                            if productSoftdib and not checkExistInRelatement:
                                self.relatement({
                                    "NMNOMEAPI"         : "woocommerce",
                                    "NMTABELA"          : "TBPRODUTO",
                                    "IDAPI"             : product_data['id'],
                                    "IDSOFTDIB"         : product_data['sku'],
                                    "DTULTIMAALTERACAO" : datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                    "HASHMD5"           : "INTEGRADO"
                                })
                
                return
            
        except Exception as e:
            print('Erro durante a sincronização de produtos:')
            traceback.print_exc()

        return super().response_code_400(req)