import time

from logging import Logger

from ctypes import (
    c_long,
    create_string_buffer,
    addressof,
)
from threading import (
    Condition,
    Thread,
)

from systools import sys_log_exception
from typing import (
    Optional,
    Dict,
)

available_sitefs = {}
configured_sitefs = {}

available_sitef_lock = Condition()

search_thread_condition = Condition()
search_thread_running = False
search_thread = None


class SitefServiceFinder(object):
    def __init__(
        self,
        connection_test_interval,   # type: int
        sitef_ip_list,              # type: []
        store_id,                   # type: str
        pdv_tef,                    # type: int
        logger,                     # type: Logger
        dll_path,                   # type: str
    ):
        # type: (...) -> None

        from ctypes import WinDLL

        self.logger = logger
        self.pdv_tef = pdv_tef
        self.store_id = store_id
        self.connection_test_interval = float(connection_test_interval)
        self.sitef_ip_list = sitef_ip_list
        self.dll_path = dll_path

        self.sitef_dll = WinDLL(dll_path)
        self.configura_sitef_interativo = self.sitef_dll["ConfiguraIntSiTefInterativo"]
        self.inicia_sitef_interativo = self.sitef_dll["IniciaFuncaoSiTefInterativo"]
        self.continua_sitef_interativo = self.sitef_dll["ContinuaFuncaoSiTefInterativo"]
        self.finaliza_sitef_interativo = self.sitef_dll["FinalizaFuncaoSiTefInterativo"]

        self._set_available_ips(
            sitef_ip_list=self.sitef_ip_list,
        )
        self.start_search_thread()

    def _set_available_ips(
        self,
        sitef_ip_list,  # type: []
    ):
        global configured_sitefs

        for ip in sitef_ip_list:
            if ip not in configured_sitefs:
                configured_sitefs[ip] = []

            if self.pdv_tef not in configured_sitefs[ip]:
                configured_sitefs[ip].append(self.pdv_tef)

    def _should_not_check_ip(
        self,
    ):
        # type: (...) -> bool

        return self.connection_test_interval <= 0

    def find_sitef_service(
        self,
    ):
        # type: (...) -> Optional[Dict]

        global available_sitefs

        self.logger.debug("Starting => find_sitef_service")

        if len(available_sitefs) == 0:
            if self._should_not_check_ip():
                self.logger.debug("No IP found in list, reload IP list")
                for sitef_ip in self.sitef_ip_list[:]:
                    if sitef_ip not in available_sitefs:
                        available_sitefs[sitef_ip] = []

                    available_sitefs[sitef_ip].append(self.pdv_tef)

            if len(available_sitefs) == 0:
                self.logger.debug("No IP was found in the list, we return empty")
                return None

        response_ip = next((ip for ip in available_sitefs if self.pdv_tef in available_sitefs[ip]), None)
        self.logger.debug("Returning IP => {0}".format(response_ip))
        return response_ip

    def sitef_unavailable(
        self,
        ip_sitef,   # type: str
    ):
        # type: (...) -> None

        global available_sitefs

        self.logger.debug("Starting => sitef_unavailable")
        self.logger.debug("Removing IP from the list of valid IPs. IP => {0}".format(ip_sitef))

        if ip_sitef in available_sitefs:
            del available_sitefs[ip_sitef]

    @staticmethod
    def lock_sitef(

    ):
        # type: (...) -> None

        global available_sitef_lock

        available_sitef_lock.acquire()

    @staticmethod
    def release_sitef(

    ):
        # type: (...) -> None

        global available_sitef_lock

        available_sitef_lock.release()

    def wakeup_search(
        self,
    ):
        # type: (...) -> None

        self.logger.debug("Starting => wakeup_search")

        global search_thread_running, search_thread_condition

        if search_thread_running:
            with search_thread_condition:
                search_thread_condition.notifyAll()

        self.logger.debug("End => wakeup_search")

    def search_sitef_modules(
        self,
    ):
        # type: (...) -> None

        global configured_sitefs, available_sitefs

        self.logger.debug("===================================================================")
        self.logger.debug("Starting => search_sitef_modules")
        self.logger.debug("Number of IPs for validation => {0}".format(len(configured_sitefs)))

        for ip in configured_sitefs.keys():
            if ip in [None, 'None', '']:
                continue

            if ip in available_sitefs:
                available_sitefs[ip] = configured_sitefs[ip]
                self.logger.debug("IP | {0} | already validate".format(ip))
                continue

            self.logger.debug("Validating IP => {0}".format(ip))
            try:
                result = self.process(
                    pos_id=str(self.pdv_tef),
                    data_fiscal=time.strftime("%Y%m%d"),
                    hora_fiscal=time.strftime("%H%M%S"),
                    ip_sitef=ip,
                )
                if result:
                    self.logger.debug("Success in validating the IP => {0}".format(ip))
                    if ip not in available_sitefs:
                        available_sitefs[ip] = []
                    available_sitefs[ip].append(self.pdv_tef)
                    continue

                self.logger.warn("Failed to validate IP => {0}".format(ip))

                if ip in available_sitefs:
                    self.sitef_unavailable(
                        ip_sitef=ip,
                    )

            except Exception:
                self.logger.exception("Exception on validate IP => {0}".format(ip))

        self.logger.debug("Finished => search_sitef_modules")
        self.logger.debug("===================================================================")

    def start_search_thread(self):

        global search_thread_running, search_thread

        if not search_thread_running:
            search_thread_running = True
            if search_thread is None:
                self.logger.debug("Starting => start_search_thread")
                search_thread = Thread(target=self._search_thread_loop)
                search_thread.daemon = True
                search_thread.name = "search_sitef_modules"
                search_thread.start()

    def stop_search_thread(self):

        if self._should_not_check_ip():
            return

        global search_thread_running, search_thread_condition

        if search_thread_running:
            with search_thread_condition:
                search_thread_running = False
                search_thread_condition.notifyAll()

    def _search_thread_loop(self):

        if self._should_not_check_ip():
            return

        global search_thread_running, search_thread_condition

        while search_thread_running:
            self.search_sitef_modules()
            with search_thread_condition:
                search_thread_condition.wait(self.connection_test_interval)

    def process(
        self,           # type: SitefServiceFinder
        pos_id,         # type: str
        data_fiscal,    # type: str
        hora_fiscal,    # type: str
        ip_sitef=None,  # type: Optional[str]
    ):
        # type: (...) -> bool

        global available_sitef_lock

        with available_sitef_lock:
            return self._process(
                pos_id=pos_id,
                data_fiscal=data_fiscal,
                hora_fiscal=hora_fiscal,
                ip_sitef=ip_sitef
            )

    def _process(
        self,           # type: SitefServiceFinder
        pos_id,         # type: str
        data_fiscal,    # type: str
        hora_fiscal,    # type: str
        ip_sitef=None,  # type: Optional[str]
    ):
        # type: (...) -> bool

        self.logger.debug("===================================================================")
        self.logger.debug("Starting => TestSitefProcessor")

        if not ip_sitef:
            self.logger.debug("Call function find_sitef_service")
            ip_sitef = self.find_sitef_service()

        if not self._check_pin_pad():
            return False

        id_terminal = "SW99%04d" % int(pos_id)
        log_message = "TestSitefProcessor - IP Sitef: {0}, ID Loja: {1}, ID Terminal: {2}"
        self.logger.debug(log_message.format(ip_sitef, self.store_id, id_terminal))

        ret = -12
        while ret == -12:
            ret = self.configura_sitef_interativo(
                str(ip_sitef),
                str(self.store_id),
                id_terminal,
                0
            )

        if ret == 0:
            ret = self.inicia_sitef_interativo(
                111,
                "",
                '{}'.format(pos_id),
                data_fiscal,
                hora_fiscal,
                "",
                ""
            )
            if ret == 10000:
                comando = c_long(0)
                tipo_campo = c_long(0)
                tam_minimo = c_long(0)
                tam_maximo = c_long(0)
                tam_buffer = 32768
                output_buffer = create_string_buffer('\000' * tam_buffer)

                while ret == 10000:
                    ret = self.continua_sitef_interativo(
                        addressof(comando),
                        addressof(tipo_campo),
                        addressof(tam_minimo),
                        addressof(tam_maximo),
                        output_buffer,
                        tam_buffer,
                        0
                    )

                    if ret != 10000:
                        break

                    try:
                        if comando.value == 20:
                            output_buffer.value = "0"
                            time.sleep(0.5)

                    except Exception as ex:
                        self.logger.exception("Exception continua sitef interativo")
                        sys_log_exception("Exception Trapped on SiTef Processor %" + str(ex))

                response = "Teste Realizado com Sucesso" if ret == 0 else "Falha no Teste: Erro %s" % str(ret)
                self.logger.debug("TestSitefProcessor - Resposta: {}".format(response))

                self.finaliza_sitef_interativo(
                    "0",
                    '{}'.format(pos_id),
                    data_fiscal,
                    hora_fiscal,
                    None
                )

                self.logger.debug("Finished => Success TestSitefProcessor")
                self.logger.debug("===================================================================")

                return True if ret == 0 else False

        self.logger.debug("Finished => TestSitefProcessor - Erro Configurando SiTef Interativo")
        self.logger.debug("===================================================================")

        return False

    def _check_pin_pad(
        self,   # type: SitefServiceFinder
    ):
        # type: (...) -> bool

        self.logger.debug("check pin pad")

        time_limit = (time.time() + 5.0)
        while time.time() < time_limit:
            if self.sitef_dll["VerificaPresencaPinPad"]():
                return True
            time.sleep(0.2)

        return False
