import threading
import time
from datetime import datetime
from typing import List, Tuple, Dict

from src.api.entities.cliente_aprovado import ApprovedClient
from src.api.entities.cliente_erp_code import ClienteErpCode
from src.jobs.system_jobs import OnlineLogger
from src.api.entities.cliente import Cliente, Endereco
from src.database import utils, queries
from src.database.queries import IntegrationType
import logging
import src.database.connection as database
from src.database.entities.client import Client
from src.entities.log import Log
import src.api.okvendas as api_okvendas
from src.database.utils import DatabaseConfig
from threading import Lock

lock = Lock()
logger = logging.getLogger()
send_log = OnlineLogger.send_log


# region Jobs

def job_send_clients(job_config: dict) -> None:
    with lock:
        """
        Job para enviar os clientes para api okvendas
        Args:
            job_config: Configuração do job obtida na api do oking
        """
        logger.info(job_config.get('job_name') + f' | Executando na Thread {threading.current_thread()}')
        try:
            db_config = utils.get_database_config(job_config)
            db_clients = get_clients(db_config, job_config.get('sql'))

            if len(db_clients) <= 0:
                send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Nenhum cliente retornado para integracao no momento', 'warning', 'CLIENTE')
                return

            #clients_inserted = insert_out_clients(db_config, db_clients)
            clients_inserted = insert_update_semaphore_client_jobs(job_config, db_config, db_clients)
            if not clients_inserted:
                send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Nao foi possivel inserir os clientes no banco semaforo', 'error', 'CLIENTE')
                return

            clients = [Cliente(
                nome=c.name,
                razao_social=c.company_name,
                sexo='M',
                data_nascimento=None,
                data_bloqueio=c.blocked_date,
                data_constituicao=None,
                cpf=c.cpf or "",
                cnpj=c.cnpj,
                endereco=Endereco(True, c.address_type, c.address, c.zipcode, c.number, c.complement, c.neighbourhood, c.city, c.state, c.residential_phone or c.mobile_phone, str(), c.reference, 'BR', c.ibge_code),
                email=c.email,
                codigo_referencia=c.client_erp,
                telefone_residencial=c.residential_phone,
                telefone_celular=c.mobile_phone,
                inscricao_estadual=c.state_registration,
                compra_liberada=c.purchase_released,
                site_pertencente=c.belonging_site,
                tipo_pessoa=c.person_type,
                limite_credito=c.credit_limit,
                codigo_representante=c.representative_code) for c in db_clients]

            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Enviando clientes via api okvendas', 'info', 'CLIENTE') ###Adicionado###
            total = len(clients)
            page = 10
            limit = 10 if total > 10 else total
            offset = 0

            partial_clients = clients[offset:limit]
            while limit <= total:
                results = api_okvendas.post_clients(partial_clients)
                if len(results) < 1:
                    send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Falha ao integrar clientes: {results[0].message}', 'error', 'CLIENTE')

                for res in results:###Adicionado###
                    identificador = [c.cpf if c.cnpj is None else c.cnpj for c in clients if res.identifiers[0].__contains__(c.cpf or c.cnpj)][0]  ###Adicionado###
                    if res.status == 1:###Adicionado###
                        if protocol_semaphore_send_clients(job_config, db_config, identificador):###Adicionado###
                            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Cliente do cpf/cnpj: {identificador} protocolado no banco semaforo', 'info', 'CLIENTE')###Adicionado###
                        else:###Adicionado###
                            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Falha ao protocolar o cliente do cpf/cnpj: {identificador}', 'warning', 'CLIENTE')###Adicionado###

                #sucessful_results = [result for result in results if result.status == 1] ###Removido###
                failed_results = [result for result in results if result.status > 1]

                #if len(sucessful_results) > 0:
                    #protocol_clients(db_config, [c.codigo_referencia for sr in sucessful_results for c in clients if sr.identifiers[0].__contains__(c.cpf or c.cnpj)])

                if len(failed_results) > 0:
                    for fr in failed_results:
                        for c in partial_clients:
                            time.sleep(0.3)
                            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Falha ao integrar cliente cod.erp {c.codigo_referencia}, cpf/cnpj {c.cnpj if c.cpf is None else c.cpf}: {fr.message}', 'error',
                                          'CLIENTE', c.codigo_referencia)

                limit = limit + page
                offset = offset + page
                partial_clients = clients[offset:limit]

        except Exception as e:
            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Erro nao esperado na integracao de clientes: {str(e)}', 'error', 'CLIENTE')


def job_send_approved_clients(job_config: dict) -> None:
    with lock:
        """
        Job para realizar o envio de clientes B2B aprovados para o ERP
    
        Args:
            job_config: Configuração do job obtida na api do oking
        """
        logger.info(job_config.get('job_name') + f' | Executando na Thread {threading.current_thread()}')
        try:
            # 1 Consultar os clientes na api
            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Consultando clientes aprovados na api okvendas', 'info', 'CLIENTE')
            clients = api_okvendas.get_approved_clients()
            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Encontrado {len(clients.data)} clientes aprovados', 'info', 'CLIENTE')

            # 2 Inserir no banco semaforo
            send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Inserindo clientes aprovados no banco semaforo', 'info', 'CLIENTE')
            db_config = utils.get_database_config(job_config)
            inserted_clients = insert_approved_clients(db_config, clients.data)

            # 3 Disparar nova procedure
            for client in [client_id for client_id, inserted in inserted_clients.items() if inserted]:
                client_data = [c for c in clients.data if c.id == client][0]
                client_erp = None
                try:
                    # 4 Receber codigo erp do cliente da procedure
                    send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Disparando procedure para o cliente {client}', 'info', 'CLIENTE')
                    client_id_sem = get_semaphore_client_id(db_config, client_data)
                    client_erp = run_approved_clients_procedure(db_config, job_config.get('db_seller'), client_id_sem)
                    if client_erp is None:
                        raise 'Codigo erp do cliente nao retornado ou nao foi possivel obter da saida da procedure'

                    # 5 Enviar codigo erp do cliente para api okvendas
                    saved = api_okvendas.put_client_erp_code(ClienteErpCode(client_data.cpf or client_data.cnpj, client_erp))
                    if saved:
                        send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Codigo erp {client_erp} do cliente {client} (id semaforo) salvo com sucesso no okvendas', 'info', 'CLIENTE')
                    else:
                        send_log(job_config.get('job_name'), True, True, f'Nao foi possivel salvar o codigo erp {client_erp} do cliente {client} no okvendas', 'error', 'CLIENTE')
                        continue

                    # 6 Protocolar no semaforo
                    protocoled = protocol_clients(db_config, [client_erp])
                    if protocoled:
                        send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Cliente {client} (id semaforo) com codigo erp {client_erp} protocolado com sucesso no banco semaforo', 'info', 'CLIENTE')
                    else:
                        send_log(job_config.get('job_name'), True, True, f'Nao foi possivel protocolar o cliente {client} (id semaforo) com codigo erp {client_erp}', 'error', 'CLIENTE')
                        continue
                except Exception as e:
                    send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f'Erro durante execucao da procedure do cliente {client} (id semaforo): {str(e)}', 'error', 'CLIENTE')

        except Exception as e:
            send_log(job_config.get('job_name'), True, True, f'Erro nao esperado na integracao de clientes aprovados: {str(e)}', 'error', 'CLIENTE')

# endregion Jobs


# region Methods

def run_approved_clients_procedure(db_config: DatabaseConfig, seller_id: int, client_id: int) -> str:
    """
    Dispara a procedure de integracao de clientes aprovados

    Args:
        seller_id: Id da loja
        db_config: Configuracao do banco de dados
        client_id: Id da tabela OPENK_SEMAFORO.CLIENTE

    Returns:
        Codigo do cliente no ERP
    """
    with database.Connection(db_config).get_conect() as conn:
        try:
            with conn.cursor() as cursor:
                if db_config.is_sql_server():
                    cursor.execute('exec openk_semaforo.sp_cliente_aprovado @cliente_id = ?, @loja_id = ?', client_id, seller_id)
                    client_erp_res = cursor.fetchone()
                    if client_erp_res is not None:
                        client_erp, = client_erp_res
                    else:
                        raise 'Nao foi possivel obter o codigo cliente_erp da procedure'
                elif db_config.is_oracle():
                    client_erp_out_value = cursor.var(str)
                    cursor.callproc('OPENK_SEMAFORO.SP_CLIENTE_APROVADO', [client_id, seller_id, client_erp_out_value])
                    client_erp = client_erp_out_value.getvalue()
                elif db_config.is_mysql():
                    result = cursor.callproc('openk_semaforo.SP_CLIENTE_APROVADO', [client_id, seller_id, (0, 'CHAR')])
                    client_erp = result[1]
        except Exception:
            conn.rollback()
            raise

        conn.commit()

    return client_erp


def insert_approved_clients(db_config: DatabaseConfig, clients: List[ApprovedClient]) -> Dict[int, bool]:
    """
    Insere os clientes aprovados no banco semaforo

    Args:
        db_config: Configuracao do banco de dados
        clients: Lista de clientes aprovados vindos da api okvendas

    Returns:
        Quantidade de clientes inseridos
    """
    with database.Connection(db_config).get_conect() as conn:
        results: Dict[int, bool] = {}
        for c in clients:
            with conn.cursor() as cursor:
                existent_client: int = None
                try:
                    existent_client = get_semaphore_client_id(db_config, c)
                except Exception:
                    pass

                address = [a for a in c.addresses if a.id == c.address_id] or c.addresses[0]  # Se nao existir um endereco na lista de enderecos que esteja relacionado com o cliente, pega o primeiro endereco ordenado
                address = address[0] if isinstance(address, list) else address
                if existent_client is None or existent_client <= 0:
                    cursor.execute(queries.get_insert_in_clients(db_config.db_type), queries.get_command_parameter(db_config.db_type, [
                        c.cpf,
                        c.cnpj,
                        datetime.now(),
                        c.name,
                        c.corporate_name,
                        c.cpf,
                        c.cnpj,
                        c.email,
                        c.home_phone,
                        c.mobile_phone,
                        address.zipcode,
                        address.type,
                        address.address,
                        address.number,
                        address.complement,
                        address.neighbourhood,
                        address.city,
                        address.state,
                        address.reference,
                        'IN',  # IN pois esta indo para o ERP
                        datetime.now(),
                        address.ibge_code,
                        c.state_registration
                    ]))
                else:
                    cursor.execute(queries.get_update_in_clients(db_config.db_type), queries.get_command_parameter(db_config.db_type, [datetime.now(), existent_client]))

                results[c.id] = cursor.rowcount > 0

        conn.commit()
        return results


def get_clients(db_config: DatabaseConfig, sql: str) -> List[Client]:
    conn = database.Connection(db_config).get_conect()
    cursor = conn.cursor()

    cursor.execute(sql)
    results = cursor.fetchall()
    columns = [desc[0] for desc in cursor.description]

    cursor.close()
    conn.close()

    clients_list = []
    new: dict = {}
    for row in results:
        for i, c in enumerate(columns):
            new[c.lower()] = row[i]

        clients_list.append(new.copy())

    clients = [Client(**c) for c in clients_list]
    return clients


def insert_out_clients(db_config: DatabaseConfig, clients: List[Client]) -> bool:
    conn = database.Connection(db_config).get_conect()
    cursor = conn.cursor()

    # cursor.executemany(queries.get_insert_out_clients(db_config.db_type), [queries.get_command_parameter(db_config.db_type, [c.client_erp, datetime.now(), datetime.now()]) for c in clients])
    for c in clients:
        cursor.execute(queries.get_insert_out_clients(db_config.db_type), queries.get_command_parameter(db_config.db_type, [c.client_erp, datetime.now(), datetime.now()]))

    result = cursor.rowcount

    cursor.close()
    conn.commit()
    conn.close()

    return result > 0


def protocol_clients(db_config: DatabaseConfig, client_erp_codes: List[str]) -> bool:
    conn = database.Connection(db_config).get_conect()
    cursor = conn.cursor()

    cursor.executemany(queries.get_out_client_protocol_command(db_config.db_type),
                       [queries.get_command_parameter(db_config.db_type, [c]) for c in client_erp_codes])
    result = cursor.rowcount

    cursor.close()
    conn.commit()
    conn.close()

    return result > 0


def insert_update_semaphore_client_jobs(job_config_dict: dict, db_config: DatabaseConfig, lists: List[Client]) -> bool:
    """
    Insere os imposts no banco semáforo
    Args:
        job_config_dict: Configuração do job
        db_config: Configuracao do banco de dados
        lists: Lista de impostos dos produtos

    Returns:
        Boleano indicando se foram inseridos 1 ou mais registros
    """
    params = [(li.cnpj if li.cpf is None else li.cpf, ' ', IntegrationType.CLIENTE.value) for li in lists]

    db = database.Connection(db_config)
    conn = db.get_conect()
    cursor = conn.cursor()
    try:
        for p in params:
            cursor.execute(queries.get_insert_update_semaphore_command(db_config.db_type), queries.get_command_parameter(db_config.db_type, list(p)))
        cursor.close()
        conn.commit()
        conn.close()
        return cursor.rowcount > 0

    except Exception as ex:
        send_log(job_config_dict.get('job_name'), job_config_dict.get('enviar_logs'), False, f' Erro ao consultar listas de clientes no banco semaforo: {str(ex)}', 'error', 'CLIENTE')

    return False


def protocol_semaphore_send_clients(job_config: dict, db_config: DatabaseConfig, identifier: str) -> bool:
    db = database.Connection(db_config)
    conn = db.get_conect()
    cursor = conn.cursor()
    print()
    try:
        if identifier is not None:
            cursor.execute(queries.get_protocol_semaphore_id_command(db_config.db_type), queries.get_command_parameter(db_config.db_type, [identifier]))
        count = cursor.rowcount
        cursor.close()
        conn.commit()
        conn.close()
        return count > 0

    except Exception as ex:
        send_log(job_config.get('job_name'), job_config.get('enviar_logs'), False, f' Erro ao protocolar cpf/cnpj do cliente no banco semaforo: {str(ex)}', 'error', 'CLIENTE')



def get_semaphore_client_id(db_config: DatabaseConfig, client_data: ApprovedClient) -> int:
    with database.Connection(db_config).get_conect() as conn:
        cursor = conn.cursor(buffered=True) if db_config.db_type.lower() == 'mysql' else conn.cursor()
        if client_data.cpf is not None and client_data.cpf != '':
            cursor.execute(queries.get_query_client_cpf(db_config.db_type), queries.get_command_parameter(db_config.db_type, [client_data.cpf]))
        elif client_data.cnpj is not None and client_data.cnpj != '':
            cursor.execute(queries.get_query_client_cnpj(db_config.db_type), queries.get_command_parameter(db_config.db_type, [client_data.cnpj]))

        res = cursor.fetchone()
        if res is None:
            raise 'Cliente nao encontrao no banco semaforo'

        cursor.close()
        client_id, = res
        return client_id

# endregion Methods
