import json
import os
import time
from typing import (
    Any,
    List,
    Optional,
)

from boss.model import User
from cfgtools import read
from message_handler_builder import Configs
from messagebus import (
    DataType,
    DefaultToken,
    Message,
    MessageBus,
    TokenCreator,
    TokenPriority,
)
from systools import sys_parsefilepath

TK_STORECFG_GET = TokenCreator.create_token(
    priority=TokenPriority.low,
    message_group="13",
    number="1",
)


class Configurator:

    def __init__(
        self,
        loader_path: str,
    ) -> None:

        self.loader_path = loader_path

        self.reader = read(file_or_path=loader_path)
        self.group = Configs.component_name.title() + "."
        self.configs = Configs()

    def load_configs(
        self,
    ) -> None:

        self.configs = Configs(
            otp_interval=self._get_otp_interval(),
            required_services=self._get_required_services(),
            api_retry_policy=self._get_api_retry_policy(),
            endpoints=self._get_endpoints(),
            database_path=self._get_databases_path(),
            default_users=self._get_default_users(),
        )

    def load_store_wide_configs(
        self,
        message_bus: MessageBus,
    ) -> None:

        self.configs.pump_credentials = Configs.PumpCredentials(
            base_url=self._read_sw_config(
                message_bus=message_bus,
                key="BackOffice.ServerURL",
            ),
            api_key=self._read_sw_config(
                message_bus=message_bus,
                key="BackOffice.ApiKey",
            ),
        )
        self.configs.store_id = self._read_sw_config(
            message_bus=message_bus,
            key="Store.Id",
        )

    def _get_endpoints(
        self,
    ) -> Configs.Endpoints:

        return Configs.Endpoints(
            authenticate=self._find(key="Endpoints.Authenticate"),
        )

    def _get_api_retry_policy(
        self,
    ) -> Configs.ApiRetryPolicy:

        return Configs.ApiRetryPolicy(
            count=int(
                self._find(
                    key="ApiRetryPolicy.Count",
                    default_value=1,
                )
            ),
            sleep=int(
                self._find(
                    key="ApiRetryPolicy.Sleep",
                    default_value=1,
                )
            ),
        )

    def _get_required_services(
        self,
    ) -> str:

        return "|".join(self.reader.find_values(path=self.group + "RequiredServices") or [])

    def _get_otp_interval(
        self,
    ) -> int:

        return int(
            self._find(
                key="OtpInterval",
                default_value=600,
            )
        )

    def _get_databases_path(
        self,
    ) -> str:

        database_path = self._find(
            key="DatabasePath",
            default_value="",
        )
        if database_path == "":
            comp_dir = os.path.dirname(self.loader_path)
            database_path = os.path.abspath(os.path.join(comp_dir, "..", "..", "databases", "users.db"))

        if database_path.startswith("file:") == -1:
            database_path = f"file:///{database_path}"

        return sys_parsefilepath(path=database_path)

    def _get_default_users(
        self,
    ) -> List[User]:

        default_users_file_path = self._find(
            key="DefaultUsersFilePath",
            default_value="",
        )

        if default_users_file_path == "":
            default_users_file_path = os.path.join(os.path.dirname(self.loader_path), "default_users.json")

        default_users_file_path = os.path.abspath(sys_parsefilepath(path=default_users_file_path))

        if not os.path.exists(default_users_file_path):
            return []

        default_users_dict = json.load(open(default_users_file_path))

        default_users: List[User] = []
        for user in default_users_dict:
            default_users.append(
                User(
                    user_id=user["UserId"],
                    user_name=user["UserName"],
                    long_name=user["LongName"],
                    password=user["Password"],
                    level=user["Level"],
                    status=user["Status"],
                    admission_date=user["AdmissionDate"],
                    termination_date=user["TerminationDate"],
                    fingerprint_minutiae=user["FingerPrintMinutiae"],
                    nfc_uuid=user["NfcUuid"],
                )
            )

        return default_users

    def _find(
        self,
        key: [str],
        default_value: Optional[Any] = None,
    ) -> Optional[str]:

        return self.reader.find_value(
            path=self.group + key,
            default=default_value,
        )

    @staticmethod
    def _read_sw_config(
        message_bus: MessageBus,
        key: str,
    ) -> str:

        for _ in range(0, 3):
            try:
                message = Message(
                    token=TK_STORECFG_GET,
                    data_type=DataType.param,
                    data=key.encode(),
                )
                msg = message_bus.send_message(
                    component="StoreWideConfig",
                    message=message,
                )
                if msg.token == DefaultToken.TK_SYS_ACK:
                    return msg.data.decode()
            except (Exception,):
                pass

            time.sleep(1)

        raise RuntimeError("Unable to get StoreWide configuration: {}".format(key))
