import logging
from datetime import datetime
from typing import (
    List,
    Optional,
)

from ._Dialog import Dialog
from ._PosState import (
    PosState,
)
from ._PosUser import PosUser
from ._PosUserState import PosUserState
from ._Service import Service
from ._UserSession import UserSession
from ._WorkingMode import WorkingMode

logger = logging.getLogger("PosModel")


class PosModel(object):
    def __init__(
        self,
        id,                          # type: int
        store_id,                    # type: str
        pos_state,                   # type: PosState
        working_mode,                # type: WorkingMode
        services,                    # type: List[Service]
        opened_users=None,           # type: Optional[List[UserSession]]
        language="pt_BR",            # type: str
        listened_events=None,        # type: Optional[List[str]]
        session_blocking_hour=None,  # type: Optional[int]
        pos_blocking_hour=None,      # type: Optional[int]
        hour_to_auto_change_day=None,  # type: Optional[int]
        auto_login_user_id=None,     # type: Optional[int]
    ):
        # type: (...) -> None
        self.id = id
        self.store_id = store_id
        self._pos_state = pos_state
        self.working_mode = working_mode
        self.services = services
        self._opened_users = opened_users or []
        self.language = language
        self.listened_events = listened_events
        self.session_blocking_hour = session_blocking_hour
        self.pos_blocking_hour = pos_blocking_hour
        self.hour_to_auto_change_day = hour_to_auto_change_day
        self.auto_login_user_id = auto_login_user_id

        self.close_day_with_open_operator = False
        self.dialogs = []
        self.custom_values = {}
        self.additional_info = {}

    @property
    def pos_state(self):
        # type: () -> PosState
        return self._pos_state

    @pos_state.setter
    def pos_state(self, value):
        self._pos_state = value

    @property
    def opened_users(self):
        # type: () -> List[UserSession]
        return self._opened_users

    @opened_users.setter
    def opened_users(self, value):
        self._opened_users = value

    @property
    def current_session(self):
        # type: () -> Optional[UserSession]
        if len(self.opened_users) == 0:
            return None

        for user in self.opened_users:
            if user.state == PosUserState.LOGGEDIN and user.pos_id == self.id:
                return user

        newest = None
        for user in self.opened_users[:]:
            if user.pos_id == self.id and (newest is None or newest.open_time <= user.open_time):
                newest = user

        return newest

    @property
    def has_opened_users(self):
        # type: () -> bool
        return any(u.state in [PosUserState.OPENED, PosUserState.PAUSED] for u in self.opened_users)

    def set_custom_value(self, key, value):
        # type: (str, str) -> None
        if value == "":
            if key in self.custom_values:
                del self.custom_values[key]
        else:
            self.custom_values[key] = value

    def get_custom_value(self, key):
        # type: (str) -> Optional[str]
        return self.custom_values.get(key)

    def add_dialog(
        self,                   # type: PosModel
        dialog,                 # type: Dialog
    ):
        # type: (...) -> None
        self.dialogs.append(dialog)

    def remove_dialog(
        self,                   # type: PosModel
        dialog_id,              # type: str
    ):
        # type: (...) -> None
        self.dialogs = [d for d in self.dialogs if d.id != dialog_id]

    def open_user(
        self,                   # type: PosModel
        pos_user,               # type: PosUser
        login_count,            # type: int
        initial_float=None,     # type: Optional[float]
    ):
        # type: (...) -> UserSession
        open_time = datetime.now()

        user_session = UserSession(
            user=pos_user,
            open_time=open_time,
            initial_float=initial_float,
            business_period=self.pos_state.business_period,
            user_control_type=self.working_mode.user_control_type,
            pos_id=self.id,
            login_count=login_count,
        )

        self._opened_users.append(user_session)
        return user_session

    @classmethod
    def from_xml(cls, xml):
        # type: (type, Element) -> PosModel
        if "posId" not in xml.attrib:
            raise ValueError()

        pos_state_node = xml.find("PosState")
        if "storeId" not in pos_state_node.attrib:
            raise ValueError()

        working_mode_node = xml.find("WorkingMode")

        opened_users = []
        user_sessions_node = xml.find("Operators")
        if user_sessions_node is not None:
            for user_session_node in user_sessions_node.findall("Operator"):
                user_session = UserSession.from_xml(xml=user_session_node)
                opened_users.append(user_session)

        used_services = cls._get_used_services(xml)
        pos_blocking_hour = cls._get_pos_blocking_hour(xml)
        auto_login_user_id = cls._get_auto_login_user_id(xml)

        language = "pt_BR"
        language_node = xml.find("Language")
        if language_node is not None:
            language = language_node.attrib.get("name", "pt_BR")

        pos_model = cls(
            id=int(xml.attrib["posId"]),
            store_id=pos_state_node.attrib["storeId"],
            pos_state=PosState.from_xml(xml=pos_state_node),
            working_mode=WorkingMode.from_xml(xml=working_mode_node),
            opened_users=opened_users,
            services=used_services,
            language=language,
            pos_blocking_hour=pos_blocking_hour,
            auto_login_user_id=auto_login_user_id,
        )

        for custom_node in xml.findall("Custom"):
            name = custom_node.attrib.get("name")
            value = custom_node.attrib.get("value")
            if name and value is not None:
                pos_model.set_custom_value(name, value)

        for dialog_node in xml.findall("PosDialog"):
            dialog = Dialog.from_xml(dialog_node)
            if dialog is None:
                continue
            pos_model.dialogs.append(dialog)

        listened_node = xml.find("ListenedEvents")
        listened_text = listened_node.text if listened_node is not None else None

        cls._build_additional_info(listened_text, pos_model, xml)

        return pos_model

    @staticmethod
    def _build_additional_info(listened_text, pos_model, xml):
        pos_model.listened_events = listened_text.split(";") if listened_text is not None else None
        for event_name in pos_model.listened_events or []:
            if not event_name:
                continue

            event_node = xml.find(event_name)
            if event_node is not None and event_node.text is not None:
                pos_model.additional_info[event_name] = event_node.text

    @staticmethod
    def _get_used_services(xml):
        used_services = []
        services_node = xml.find("UsedServices")
        if services_node is not None:
            for service_node in services_node.findall("Service"):
                service_type = service_node.attrib.get("type")
                service_name = service_node.attrib.get("name")
                service_default = service_node.attrib.get("default", "false").lower() == "true"
                if service_type and service_name:
                    used_services.append(Service(service_type, service_name, service_default))
        return used_services

    @staticmethod
    def _get_auto_login_user_id(xml):
        # type: (Element) -> Optional[int]
        node = xml.find("AutoLoginUserId")
        if node is not None and node.text:
            return int(node.text)
        return None

    @staticmethod
    def _get_pos_blocking_hour(xml):
        # type: (Element) -> Optional[int]
        node = xml.find("PosBlockingHour")
        if node is not None and node.text:
            return int(node.text)
        return None
