# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 Avencall
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>


import logging
import time

from phpserialize import *
from xivo_dao import session_dao, admin_dao
from xivo_dao.helpers.db_utils import session_scope

from xivo_auth import BaseAuthenticationBackend
from xivo_auth.plugins.backends.webi_perms_to_acl import WEBI_PERMS_TO_CONFD_ACL

logger = logging.getLogger(__name__)


class XiVOSession(BaseAuthenticationBackend):

    def get_acls(self, login, args):
        with session_scope():
            username = self._get_username(args['cookie'])
            if self._is_root(username):
                return ['confd.#']
            else:
                return self._get_acl_for_webi_user(username)

    def get_ids(self, username, args):
        with session_scope():
            username = self._get_username(args['cookie'])
            auth_id = str(admin_dao.get_admin_id(username))
        user_uuid = None
        return auth_id, user_uuid

    def verify_password(self, login, password, args):
        cookie = args['cookie']
        if cookie is not None:
            with session_scope():
                current_timestamp = int(time.time())
                session_obj = session_dao.get_session(args['cookie'])
                if current_timestamp < session_obj.expire and self._deserialize_username(session_obj.data) is not None:
                    return True
                else:
                    return False
        else:
            return False

    def _get_username(self, cookie):
        session_obj = session_dao.get_session(cookie)
        username = self._deserialize_username(session_obj.data)
        logger.debug("Retrieved username %s " % username)
        return username if username is not None else None

    def _get_acl_for_webi_user(self, username):
        webi_user_perms = self._get_webiuser_permission(username)
        return self._get_acl_from_webi_perms(webi_user_perms)

    def _get_webiuser_permission(self, username):
        webi_user_data = admin_dao.get_admin_permission(username)
        return self._deserialize_webi_permission(webi_user_data)

    def _deserialize_webi_permission(self, webi_user_data):
        try:
            if isinstance(webi_user_data, str):
                webi_user_data = webi_user_data.encode('utf-8')
            return loads(webi_user_data)
        except Exception as e:
            logger.error("Failed to deserialize user data: %s " % str(e))
            return None

    def _get_acl_from_webi_perms(self, webi_user_perms):
        users_acl = []

        if not webi_user_perms:
            return users_acl

        webi_user_perms_flattened = self._flatten_webi_user_perms(webi_user_perms)
        for webi_path, authorization in webi_user_perms_flattened.items():
            if authorization:
                if webi_path in WEBI_PERMS_TO_CONFD_ACL:
                    users_acl.append(WEBI_PERMS_TO_CONFD_ACL[webi_path])
                else:
                    logger.debug("Couldn't get confd ACL for webi path %s: mapping undefined" % webi_path)

        return list(set(users_acl))

    def _flatten_webi_user_perms(self, webi_user_perms):
        logger.error(webi_user_perms)

        def flatten(d, parent_key='', sep='.'):
            items = []
            for k, v in d.items():
                k = k.decode('utf-8') if isinstance(k, bytes) else k
                new_key = parent_key + sep + k if parent_key else k
                if isinstance(v, dict):
                    items.extend(list(flatten(v, new_key, sep=sep).items()))
                else:
                    items.append((new_key, v.decode('utf-8') if isinstance(v, bytes) else v))
            return dict(items)

        return flatten(webi_user_perms)

    @staticmethod
    def _deserialize_username(session_data):
        try:
            serialized_data = session_data.split('_USR|', 5)[1].split("_WEBI", 5)[0]
            if isinstance(serialized_data, str):
                serialized_data = serialized_data.encode('utf-8')
            login = loads(serialized_data, object_hook=phpobject)._asdict()[b'login'].decode('utf-8')
            return login
        except Exception as e:
            logger.error("Failed to deserialize session data: %s " % repr(e))
            return None

    @staticmethod
    def _is_root(username):
        if username == 'root':
            return True
        elif username is None:
            raise ValueError('username must not be None')
        else:
            return False
