import json
import uuid
import logging
import datetime

from django.conf import settings
from django.utils import timezone
from django.utils.functional import cached_property

from intranet.search.core.tvm import tvm2_client
from intranet.search.core.utils import http
from requests import HTTPError

log = logging.getLogger(__name__)

ORGANIZATION_EVENTS = {'organization_added', 'organization_migrated', 'organization_deleted'}
DOMAIN_EVENTS = {'domain_master_changed'}
SERVICE_EVENTS = {'service_enabled', 'service_disabled'}
USER_EVENTS = {
    'user_added',
    'user_property_changed',
    'user_group_added',
    'user_group_deleted',
    'user_moved',
    'user_alias_added',
    'user_alias_deleted',
}
USER_DELETED_EVENT = 'user_dismissed'
GROUP_PROPERTY_CHANGED_EVENT = 'group_property_changed'
DEPARTMENT_PROPERTY_CHANGED_EVENT = 'department_property_changed'
DEPARTMENT_EVENTS = {
    'department_added',
    'department_deleted',
    'department_user_added',
    'department_user_deleted',
    'department_alias_added',
    'department_alias_deleted',
    DEPARTMENT_PROPERTY_CHANGED_EVENT,
}
GROUP_EVENTS = {
    'group_added',
    'group_deleted',
    'group_membership_changed',
    'group_alias_added',
    'group_alias_deleted',
    GROUP_PROPERTY_CHANGED_EVENT,
}

TYPE_GROUP = 'group'
TYPE_DEPARTMENT = 'department'
GROUP_GENERIC_TYPE = 'generic'


class ObjectDoesNotExist(HTTPError):
    pass


def join_fields(fields, prefix=''):
    tmpl = f'{prefix}.{{}}' if prefix else '{}'
    return ','.join(tmpl.format(f) for f in fields)


class DirectoryApiClient:
    """ Клиент апи директории
    """
    PER_PAGE = 1000
    ORGANIZATION_FIELDS = 'id,name,label,revision,services,type,organization_type'

    @property
    def api(self):
        return settings.ISEARCH['api']['directory']

    @cached_property
    def session(self):
        return http.create_session()

    def get_request_headers(self, organization_id=None, request_id=None, ticket=None):
        headers = self.session.headers.copy()

        if not ticket:
            ticket = tvm2_client.get_service_ticket('directory')
        headers[settings.TVM2_SERVICE_HEADER] = ticket

        if request_id is None:
            request_id = str(uuid.uuid4())
        headers['X-Request-Id'] = request_id

        if organization_id is not None:
            headers['X-Org-ID'] = str(organization_id)

        return headers

    def _do_request(self, url, method=None, organization_id=None, **kwargs):
        kwargs['headers'] = self.get_request_headers(organization_id)

        method = method or self.session.get
        try:
            response = http.call_with_retry(method, url, verify=False, **kwargs)
        except HTTPError as e:
            log.exception(
                'Something went wrong with %s to "%s". directory_request_id: %s',
                method.__name__, url, kwargs['headers']['X-Request-Id'],
            )
            if e.response.status_code == 404:
                raise ObjectDoesNotExist(*e.args)
            raise
        return response.json()

    def fetch_page(self, url, page=1, params=None, **kwargs):
        params = params or {}
        params.setdefault('per_page', self.PER_PAGE)
        params.setdefault('page', page)
        return self._do_request(url, params=params, **kwargs)

    def get_url(self, url_type, obj_id=None, fields='', **kwargs):
        query = {'fields': fields}
        query.update(kwargs)
        url = self.api[url_type].url(query=query)
        return url.format(id=obj_id) if obj_id else url

    def get_users(self, organization_id, **kwargs):
        """ Возвращает ифнормацию о списке пользователей организации
        """
        url = self.get_url('users', **kwargs)
        return self.fetch_page(url, organization_id=organization_id)

    def get_user(self, uid, organization_id, **kwargs):
        """ Возвращает ифнормацию о об одном пользователе организации
        """
        url = self.get_url('user', uid, **kwargs)
        return self._do_request(url, organization_id=organization_id)

    def get_organizations(self, fields=ORGANIZATION_FIELDS, **kwargs):
        """ Возвращает ифнормацию о списке организаций
        """
        url = self.get_url('organizations', fields=fields, **kwargs)
        return self.fetch_page(url)

    def get_organization(self, organization_id, fields=ORGANIZATION_FIELDS):
        """ Возвращает ифнормацию об одной организации
        """
        url = self.get_url('organization', organization_id, fields=fields)
        return self._do_request(url)

    def get_department(self, department_id, organization_id, **kwargs):
        """ Возвращает информацию об одном департаменте
        """
        url = self.get_url('department', department_id, **kwargs)
        return self._do_request(url, organization_id=organization_id)

    def get_departments(self, organization_id, **kwargs):
        """ [Возвращает список департаментов организации] outdated
        Не поддерживается, начиная с ISEARCH-7356
        """
        return {}

    def get_group(self, group_id, organization_id, **kwargs):
        """ Возвращает информацию об одной группе
        """
        url = self.get_url('group', group_id, **kwargs)
        return self._do_request(url, organization_id=organization_id)

    def get_groups(self, organization_id, **kwargs):
        """ [Возвращает список групп организации] outdated
        Не поддерживается, начиная с ISEARCH-7356
        """
        return {}

    def add_webhook(self, event_url, event_names=None):
        """ Добавляет подписку на событие
        """
        url = self.get_url('webhooks')
        data = json.dumps({'url': event_url, 'event_names': event_names or []})
        return self._do_request(url, method=self.session.post, data=data,
                                headers={'Content-Type': 'application/json'})

    @staticmethod
    def validate_robot_uid_response(response):
        try:
            result = response['result']
            result_count = len(result)
            user = result[0]
            is_robot = user['is_robot']
            uid = user.get('id')
        except (AttributeError, TypeError, KeyError):
            return 'Invalid response format'

        if result_count != 1:
            return f'Invalid user count in result {result_count}'

        if not is_robot:
            return 'User is not a robot'

        if uid is None:
            return 'User has no uid'

        return None

    def get_robot_user_uid(self, organization_id):
        """ Получает uid роботного пользователя в данной организации
        """
        url = self.get_url('users', fields='id,is_robot',
                           **{'nickname': 'robot-%s' % settings.ISEARCH_SLUG_IN_DIRECTORY})
        response = self._do_request(url, organization_id=organization_id)

        validation_error = self.validate_robot_uid_response(response)
        if validation_error is not None:
            message = (
                f'Directory return invalid robot response for org {organization_id}. '
                f'Error: {validation_error}'
            )
            raise RuntimeError(message)

        return response['result'][0]['id']

    def get_robot_user_token(self, uid):
        """ Получает oauth токен роботного пользователя для директории
        """
        url = self.api['robot_token'].url().format(uid=uid)
        response = self._do_request(url)
        expires_at = timezone.now() + datetime.timedelta(seconds=response['expires_in'])
        return response['token'], expires_at
