from threading import Lock
from typing import Optional, Any, List
from datetime import datetime

from application.model import UpdateType, UserUpdateData, PosUser
from application.model.configuration import Configurations
from application.model.customexception import (
    ErrorApplyingUpdate,
    DisabledFunctionality,
    SomeUserLogged,
    ErrorRetrievingUsers,
)
from application.repository.api import UserUpdaterAPIRepository
from application.repository.pos import UserUpdaterPOSRepository
from application.util import LoggerUtil, settings

logger = LoggerUtil.get_logger_name(__name__)


class UserUpdaterInteractor(object):
    def __init__(self, configs, working_on_update, api_repository, pos_repository, enabled):
        # type: (Configurations, Lock, Optional[UserUpdaterAPIRepository], UserUpdaterPOSRepository, bool) -> None

        self.logger = logger
        self.configs = configs
        self.update_type = UpdateType.user
        self.update_name = self.update_type.name
        self.local_configs = configs.updaters[self.update_name]
        self.api_repository = api_repository
        self.pos_repository = pos_repository
        self.working_on_update = working_on_update
        self.enabled = enabled

    def update_users(self, logged_users):
        # type: (List[int]) -> None

        settings.set_update_control(self.update_name, None)
        if not self.enabled:
            settings.set_update_control(self.update_name, "$UPDATE_DISABLED")
            raise DisabledFunctionality()
        try:
            self.logger.info("Waiting Updating Users")
            with self.working_on_update:
                self.logger.info("Updating Users")

                self._apply_update(logged_users)

                self.logger.info("Update fully concluded with success!")
        except (Exception,):
            self.logger.exception("Not expected exception on user update")
            raise

    def _get_updated_users(self):
        # type: () -> List[UserUpdateData]

        self.logger.info("Trying to obtain employees data...")
        updated_users = []

        received_employee_data = self.api_repository.get_employees_data()

        for user in received_employee_data:
            user_data = UserUpdateData(
                user_id=int(user.get("pos-user-id")),
                long_name=user.get("name").encode("utf-8"),
                password=user.get("pos-password"),
                nfc_id_code=user.get("nfc-id-code") or user.get("pos-password"),
                level=user.get("pos-access-level").get("code"),
                start_date=user.get("from-dt"),
                end_date=user.get("to-dt"),
                pay_rate=user.get("pay-rate-amount"),
            )
            user_data.fill_user_status()
            updated_users.append(user_data)

        self.logger.info("Package successfully obtained!")

        return updated_users

    def _apply_update(self, logged_users):
        # type: (Any) -> None

        try:
            self.logger.info("Applying update on database...")
            settings.set_update_control(self.update_name, "$GETTING_USERS_FROM_BACKOFFICE")
            settings.set_update_control("updating", True)

            updated_users = self._get_updated_users()
            settings.set_update_control(self.update_name, "$UPDATING_POS_USERS")
            pos_users = self.pos_repository.get_pos_users()
            users_to_disable = self._get_users_to_disable(updated_users, pos_users)
            if len(users_to_disable) > 0:
                users_disable_logged = self._get_users_disable_logged(users_to_disable, logged_users)
                if len(users_disable_logged) > 0:
                    raise SomeUserLogged(self._get_logged_user_names(pos_users, users_disable_logged))

                self.pos_repository.disable_users(users_to_disable)

            self.pos_repository.update_users(updated_users)

            last_user_update_id = self.pos_repository.get_last_user_update_id()
            self.pos_repository.insert_new_user_update(str(last_user_update_id + 1), "Users")

            settings.set_update_control(self.update_name, "$SUCCESS_UPDATE")
            self.logger.info("Package successfully applied!")
            self.pos_repository.mb_context.MB_EasyEvtSend("USERS_AND_LEVELS_MODIFIED", '', str(datetime.now()))
        except ErrorRetrievingUsers as ex:
            settings.set_update_control(self.update_name, ex.message)
            raise ErrorRetrievingUsers(ex)
        except SomeUserLogged as ex:
            self.logger.exception("Users {} are logged".format(ex.message))
            settings.set_update_control(self.update_name, "$SOME_USER_LOGGED")
            raise
        except Exception as ex:
            self.logger.exception("Error applying update")
            settings.set_update_control(self.update_name, "$UNKNOWN_ERROR")
            raise ErrorApplyingUpdate(ex)
        finally:
            settings.set_update_control("updating", False)

    def _get_users_to_disable(self, updated_users, pos_users):
        # type: (List[UserUpdateData], List[PosUser]) -> List[str]

        users_to_disable = []

        removed_users = self._get_removed_users(updated_users, pos_users)
        users_to_disable.extend(removed_users)

        expired_users = self._get_expired_users(updated_users, pos_users)
        users_to_disable.extend(expired_users)

        return users_to_disable

    @staticmethod
    def _get_removed_users(updated_users, pos_users):
        # type: (List[UserUpdateData], List[PosUser]) -> List[str]

        updated_users_id = [x.user_id for x in updated_users]
        removed_users = [str(x.user_id) for x in pos_users if int(x.level) <= 10 and x.user_id not in updated_users_id]
        return removed_users

    def _get_expired_users(self, updated_users, pos_users):
        # type: (List[UserUpdateData], List[PosUser]) -> List[str]

        today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
        users_with_end_date = [x for x in updated_users if x.end_date]
        expired_users = [x.user_id for x in users_with_end_date if self._parse_date(x.end_date) <= today]
        return [str(x.user_id) for x in pos_users if int(x.level) <= 10 and x.user_id in expired_users]

    @staticmethod
    def _parse_date(date):
        # type: (str) -> datetime

        return datetime.strptime(date, '%Y-%m-%d')

    @staticmethod
    def _get_logged_user_names(pos_users, logged_users):
        # type: (List[PosUser], List[int]) -> str

        user_names = [str(x.user_id) + " - " + x.long_name for x in pos_users if x.user_id in logged_users]
        return "\\".join(user_names)

    @staticmethod
    def _get_users_disable_logged(users_to_disable, logged_users):
        # type: (List[int], List[int]) -> List[int]

        return [int(x) for x in logged_users if str(x) in users_to_disable]
