# -- coding: utf-8 --
import re, json, time
import base64
import traceback
from   generic.gn_api       import    gn_api     
from   generic.gn_request   import    Request     
#from   datetime             import    datetime         
#from   time                 import    sleep     

class gtiiot (gn_api):
    """Classe responsavel pela integracao com a plataforma GtiIot 
    """
    def __init__(self, argv:list):
        
        self.db = False
        self.setup(argv)
        self.url = self.get_token_data('URL')


        token = self.oauth_token()

        if not token:
            self.createLog(401, 'Nao foi possível autenticar no sistema da GTIIOT')
            exit()

        self.gtiiot_request = Request(
                base_url = f"{self.url}/api/v1.1/erp/"
            ,   headers  = {
                "Content-Type"     : "application/json",
                "Accept"           : "*/*",
                "Authorization"    : f"Bearer {token.get('access_token')}"   
            }
        ) 
        
        super(gtiiot, self, ).__init__()

    def oauth_token(self):
        import base64
        from urllib3.exceptions import NewConnectionError, MaxRetryError


        self.clientID = self.get_token_data('ClientID')
        self.clientSecret = self.get_token_data('ClientSecret')
        self.authUrl = self.get_token_data('AuthURL')

        
        Authorization = "Basic " + base64.b64encode(bytes(f"{self.clientID}:{self.clientSecret}", encoding='utf8')).decode("utf-8")
        
        req = Request(
            method='POST',
            verify=False,
            headers={
                "Authorization": Authorization,
                "Content-Type": "application/x-www-form-urlencoded",
            },
            data={
                "grant_type": "client_credentials",
                "scope": "erp.read erp.write"
            }
        ).doRequest(url=self.authUrl)

        if req.ok: 
            return req.json()
        else:
            return False

    #-------------------------------------------------------------------#
    #--------------- TRATAMENTO CUSTOM SQL - PRODUTOS  -----------------#
    #-------------------------------------------------------------------#

    def gti_flag_ativo(self, field):
        # FGSITUACAO = 0 -> ativo (mesma regra usada na lojainteg)
        return f"(SELECT IF({field['dbfield']} = 0, 'true', 'false'))"

    def gti_flag_excluido(self, field):
        # FGEXCLUIDO e varchar(5): normaliza as variacoes possiveis
        return f"(SELECT IF(UPPER(TRIM({field['dbfield']})) IN ('S','SIM','TRUE','1'), 'true', 'false'))"

    #--------------------------------------------------------#
    #----------- TRATAMENTO CUSTOM SQL - CLIENTE  -----------#
    #--------------------------------------------------------#
    
    def gti_flag_nao_excluido(self, field):
        # Cliente nao tem FGSITUACAO: ativo = nao excluido (inverso do gti_flag_excluido)
        return f"(SELECT IF(UPPER(TRIM(COALESCE({field['dbfield']}, ''))) IN ('S','SIM','TRUE','1'), 'false', 'true'))"

    def gti_documento(self, field):
        # VLCPFCNPJ e bigint e perde zeros a esquerda: completa 14 (PJ) ou 11 (PF)
        return """(SELECT IF(
                        COALESCE(TBCLIENTE.VLCPFCNPJ, 0) = 0
                    ,   NULL
                    ,   LPAD(TBCLIENTE.VLCPFCNPJ, IF(TBCLIENTE.FGTIPOPESSOA = 'J', 14, 11), '0')
                ))"""

    def gti_telefone(self, field):
        # Formata como (DD) NUMERO; sem DDD envia so o numero; sem telefone envia vazio
        return """(SELECT IF(
                        COALESCE(TBCLIENTE.VLTELEFONE, 0) = 0
                    ,   NULL
                    ,   CONCAT_WS(' ', IF(COALESCE(TBCLIENTE.VLDDD, 0) = 0, NULL, CONCAT('(', TBCLIENTE.VLDDD, ')')), TBCLIENTE.VLTELEFONE)
                ))"""

    def gti_endereco(self, field):
        # Junta tudo em uma linha; NULLIF descarta partes vazias (CONCAT_WS ja ignora NULL)
        return """(SELECT REPLACE(CONCAT_WS(' - '
                    ,   NULLIF(CONCAT_WS(', ', NULLIF(TRIM(TBCLIENTE.NMENDERECO), ''), NULLIF(TBCLIENTE.VLENDERECONUMERO, 0)), '')
                    ,   NULLIF(TRIM(TBCLIENTE.NMCOMPLEMENTO), '')
                    ,   NULLIF(TRIM(TBCLIENTE.NMBAIRRO), '')
                    ,   NULLIF(CONCAT_WS('/', NULLIF(TRIM(TBCLIENTE.NMCIDADE), ''), NULLIF(TRIM(TBCLIENTE.NMUF), '')), '')
                    ,   IF(COALESCE(TBCLIENTE.VLCEP, 0) = 0, NULL, CONCAT('CEP ', LPAD(TBCLIENTE.VLCEP, 8, '0')))
                ), CHAR(34), CHAR(39)))"""

    #--------------------------------------------------------#
    #-------- SINCRONIZAR PRODUTOS  -------------------------#
    #--------------------------------------------------------#

    def format_rote_sync_products(self, rote):
        return "product"

    def afterGet_sync_products(self, data):
        return data['records']
    
    #--------------------------------------------------------#
    #-------- SINCRONIZAR MAQUINAS  -------------------------#
    #--------------------------------------------------------#

    def format_rote_sync_machines(self, rote):
        return "machine"

    def afterGet_sync_machines(self, data):
        return data['records']

    #--------------------------------------------------------#
    #-------- SINCRONIZAR PRODUTOS  -------------------------#
    #--------------------------------------------------------#

    def gti_sync_handler(self, data):
        """
            Cria a TBRELACIONAID dos registros que ja existem na GTI.
            - So relaciona codigos que existem na base local (evita o DELETE inativar o que nao e nosso)
            - Nao mexe em relacoes existentes (nao altera o hash de quem ja foi enviado)
        """
        table   = self.atualWs['table']
        pk      = self.processItem_dbfield({'dbfield': self.atualWs['primaryKey']})
        aceitos = set(self.atualWs.get('external_sources') or [self.gti_default_source()])

        # Codigos locais, com o mesmo where da rota de envio (@empresa/@filial)
        locais = {
            str(row['softdib_id']).strip()
            for row in self.select(f"SELECT {pk} AS softdib_id FROM {table} {self.mountWhere([])}")
        }

        # Codigos ja relacionados para esta api/tabela
        ja_relacionados = {
            str(row['IDSOFTDIB']).strip()
            for row in self.select(f"""
                SELECT IDSOFTDIB FROM TBRELACIONAID
                WHERE NMTABELA = '{table}' AND NMNOMEAPI = '{self.apiName}'
            """)
        }

        criados = ignorados = 0
        for item in data:
            source = str(item.get('external_source') or '').strip()
            key    = str(item.get('external_key')    or '').strip()

            # Como key precisa estar em 'locais' e source em 'aceitos',
            # so valores nossos chegam ao SQL
            if source not in aceitos or key not in locais or key in ja_relacionados:
                ignorados += 1
                continue

            self.relatement({
                    "NMNOMEAPI"         : self.apiName
                ,   "NMTABELA"          : table
                ,   "IDSOFTDIB"         : key
                ,   "IDAPI"             : f"{source}/{key}"
                ,   "DTULTIMAALTERACAO" : "now()"
                # Forca 1 reenvio no proximo SEND (upsert), alinhando a GTI com o ERP
                ,   "HASHMD5"           : "NextUpdateWillGenerateNewHash"
            })

            ja_relacionados.add(key)  # mesma chave em 2 sources: relaciona so a primeira
            criados += 1

        self.createLog("200", f"[{table}] GTI lidos: {len(data)} | relacionados: {criados} | ignorados: {ignorados}")

    #--------------------------------------------------------#
    #-------- CONSULTA GTI PARA CRIAR O RELACIONA ID --------#
    #--------------------------------------------------------#
    def gti_get_params(self, page=1, cursor=None):
        """Monta os parametros de consulta e guarda a pagina atual para a paginacao."""
        params  = {'page_size': self.atualWs.get('page_size', 1000)}

        if cursor:
            params['cursor'] = cursor
        else:
            params['page'] = page

        self._gti_page = page
        return params

    #--------------------------------------------------------#
    #--------------- TRATAMENTO CUSTOM SQL  -----------------#
    #--------------------------------------------------------#
    def gti_flag_in(self, field):
        """
            Flag generica de ativo/inativo, configurada pelo map.json.

            Uso no map.json:
                { "dbfield": "TBMAQUINAEQUIP.FGSITUACAO", "para": "is_active",
                  "_custom": "gti_flag_in", "active_values": ["A"] }

            - field['dbfield']       -> coluna avaliada
            - field['active_values'] -> valores considerados ativos (comparacao sem
                                        diferenciar maiusculas e ignorando espacos)

            Retorna um trecho SQL que resulta em 'true' ou 'false' (string, como a GTI espera).
            Coluna nula vira '' e, portanto, 'false', a menos que '' esteja em active_values.
        """
        valores = field.get('active_values') or []

        # Sem valores configurados nao ha como decidir: loga e trata tudo como ativo
        if not valores:
            self.createLog("999", f"gti_flag_in sem 'active_values' no campo '{field.get('para')}' da rota {self.atualWs.get('rote')}")
            return "(SELECT 'true')"

        # Valores vem do map.json (fonte confiavel); escapa aspas simples por seguranca
        lista = ", ".join("'{}'".format(str(v).upper().replace("'", "''")) for v in valores)

        return f"(SELECT IF(UPPER(TRIM(COALESCE({field['dbfield']}, ''))) IN ({lista}), 'true', 'false'))"

    #--------------------------------------------------------#
    #------------------ TRATAMENTO RESPONSE  ----------------#
    #--------------------------------------------------------#
    def response_code_200(self, response):
        # GTI responde 200 quando todos os itens foram aceitos -> grava TBRELACIONAID
        return super().response_code_201(response)

    def custom_control_id(self, response):
        # Identidade na GTI = external_source + external_key (mesmo formato gravado pelo gti_sync_handler)
        source = str(self.current_data.get('external_source') or 'softdib').strip()
        key    = str(self.current_data.get('external_key')    or '').strip()
        return f"{source}/{key}" if key else None

    def response_code_207(self, response):
        # Sucesso parcial: como enviamos 1 item por request, tratamos como erro e logamos
        self.response_log(response)
        return False

    #--------------------------------------------------------#
    #------------------------ REGRAS DE PAGINACAO ------------#
    #--------------------------------------------------------#

    def pagination_rules(self, response, data):
        """Retorna a rota da proxima pagina ou None para parar."""
        body      = self.tryJson(response)
        page_size = int(self.atualWs.get('page_size', 1000))

        sync_rotes = {
            "sync_products": "product",
            "sync_machines": "machine"
        }

        rote = sync_rotes.get(self.atualWs['rote'], self.atualWs['rote']) 

        if isinstance(body, dict):
            # 1) Paginacao por cursor, se a API devolver
            cursor = body.get('next_cursor') or body.get('cursor_next')
            if cursor:
                return f"{rote}?{self.mountRouteParams(self.gti_get_params(cursor=cursor))}"

            # 2) Padrao DRF: 'next' nulo = ultima pagina
            if 'next' in body:
                if not body.get('next'):
                    return None
                return f"{rote}?{self.mountRouteParams(self.gti_get_params(page=self._gti_page + 1))}"

        # 3) Sem metadados: pagina incompleta = ultima pagina
        if len(data) < page_size:
            return None
        return f"{rote}?{self.mountRouteParams(self.gti_get_params(page=self._gti_page + 1))}"


    #--------------------------------------------------------#
    #------------------------ DELETE ------------------------#
    #--------------------------------------------------------#
    def deleteFromApi(self, **item):
        # Exclusao logica: DELETE /{rota}/{external_source}/{external_key}/
        # IDAPI pode ser "source/key" (product/machine) ou so "key" (customer e relacoes antigas)
        idapi = str(item[self.application_id]).strip().strip('/')
        if '/' not in idapi:
            idapi = f"softdib/{idapi}"

        res = self.throttling(
                rote   = f"{self.atualWs['rote']}/{idapi}/"
            ,   method = "DELETE"
        )
        return res.status_code in range(200, 300)
    
    def __del__(self):
        self.createLog("999","Final da exportacao")

if __name__ == "__main__":
    pass


