import logging

from tornado.httpclient import HTTPResponse

from intranet.search.core.utils import format_directory_group
from intranet.search.core.sources.directory.client import DirectoryApiClient, TYPE_DEPARTMENT, TYPE_GROUP
from intranet.search.core.storages.organization import SERVICE_DIRECTORY
from intranet.search.abovemeta import errors
from intranet.search.abovemeta.request import Request
from intranet.search.abovemeta.steps.base import (
    HttpStep,
    timeouts,
    FoundDocsConditionalHttpStepMixin,
)


log = logging.getLogger(__name__)


class DirectoryException(Exception):
    def __init__(self, code):
        self.code = code


class DirectoryBaseStep(HttpStep):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.api = DirectoryApiClient()

    def get_request_id(self, state):
        return f'{state.request_id}-{self.request_name}'


class DirectoryStep(DirectoryBaseStep):
    request_type = 'directory'
    request_name = 'user'

    def get_cache_key(self, state):
        return ':'.join((self.request_type, self.request_name,
                         str(state.org_directory_id or 0),
                         str(state.requested_org_directory_id or 0),
                         str(state.user_uid)))

    def get_request(self, state):
        user_headers = {
            'X-UID': str(state.user_uid),
            'X-User-IP': state.user_ip or state.remote_ip,
        }
        if state.tvm_auth_user_ticket:
            user_headers['X-Ya-User-Ticket'] = state.tvm_auth_user_ticket

        if state.org_directory_id:
            user_headers['X-Org-Id'] = str(state.org_directory_id)

        request_id = self.get_request_id(state)
        service_ticket = state.tvm_service_tickets['directory']
        headers = self.api.get_request_headers(request_id=request_id, ticket=service_ticket)
        headers.update(user_headers)

        url = self.get_request_url(state)
        return Request(
            url=url,
            type_=self.request_type,
            name=self.request_name,
            headers=headers,
            request_timeout=timeouts['groups'],
        )

    def set_data(self, state, data):
        try:
            data = self.parse_response_data(state, data)
        except DirectoryException as e:
            state.set_error(e.code)
            return

        if not data:
            self.set_error(state)
            return

        if not state.org_directory_id:
            state.org_directory_id = data['org_id']

        state.is_admin_user = data['is_admin']
        if data.get('organization'):
            state.is_cloud_organization = data['organization']['organization_type'] == 'cloud'

        type_to_groups = {
            TYPE_GROUP: data.get('groups', []),
            TYPE_DEPARTMENT: data.get('departments', []),
        }

        groups = set()
        for group_type, directory_groups in type_to_groups.items():
            for directory_group in directory_groups:
                group_object = {'type': group_type, 'id': directory_group['id']}
                group = format_directory_group(group_object)
                groups.add(group)

        state.groups = sorted(groups)

        user_services = {s['slug'] for s in data['services']}
        user_services.add(SERVICE_DIRECTORY)
        log.info('Got directory data: user_services=%s, user_groups=%s',
                 user_services, state.groups, extra={'state': state})
        state.apply_search_permissions(user_services)

    def set_error(self, state, response=None):
        try:
            error = response.get('message') or response.error
        except AttributeError:
            error = 'Cannot get organization and groups for user %s' % state.user

        request_id = self.get_request_id(state)

        extra_data = {'stack': True, 'state': state}
        if isinstance(response, HTTPResponse):
            extra_data['response'] = {'body': response.body, 'code': response.code}
        else:
            extra_data['response'] = response
        log.error('%s. directory_request_id: %s', error, request_id, extra=extra_data)
        state.set_error(errors.ERROR_DIRECTORY, error)

    def get_request_url(self, state):
        if state.user_has_org:
            fields = 'groups,department,org_id,departments,services,is_admin,organization.organization_type'
            url = self.api.get_url('user', state.user_uid, fields=fields)
        else:
            # если у пользователя нет организации, то он может быть внешним админом,
            # поэтому делаем для него другой запрос
            fields = 'id'
            url = self.api.get_url('organizations', fields=fields, uid=state.user_uid)
        return url

    def parse_response_data(self, state, data):
        # У внешних админов совершенно другой формат ответа, поэтому разбираем его отдельно
        log.info('Got directory data: %s', data, extra={'state': state})

        if state.user_has_org:
            return data

        if not data or not data.get('result'):
            log.error('User does not have any organization', extra={'state': state})
            raise DirectoryException(code=errors.REDIRECT_USER_ORGANIZATION)

        org_data = data.get('result', [])
        if state.requested_org_directory_id:
            org_data = [d for d in data.get('result', [])
                        if d['id'] == state.requested_org_directory_id]

        if not org_data:
            log.error('User does not have organization %s', state.requested_org_directory_id,
                      extra={'state': state})
            raise DirectoryException(code=errors.ERROR_UNKNOWN_ORGANIZATION)
        if len(org_data) > 1:
            # если найдено более одной организации, то нужно уточнить организацию
            log.error('User has several organizations: %s', data, extra={'state': state})
            raise DirectoryException(code=errors.ERROR_SPECIFY_ORGANIZATION)

        org_data = org_data[0]
        data = {
            'org_id': org_data['id'],
            'is_admin': True,
            'services': [],
        }
        return data


class AvatarsStep(FoundDocsConditionalHttpStepMixin, DirectoryBaseStep):
    request_type = 'directory'
    request_name = 'avatars'

    search = 'directory'
    index = ''

    def get_request(self, state, docs):
        headers = self.api.get_request_headers(
            organization_id=state.org_directory_id,
            request_id=self.get_request_id(state),
            ticket=state.tvm_service_tickets['directory'],
        )

        uids = ','.join(str(d.raw_snippet['uid']) for d in docs)
        url = self.api.get_url('users', fields='id,avatar_id', id=uids, per_page=len(uids))

        return Request(
            url=url,
            type_=self.request_type,
            name=self.request_name,
            headers=headers,
            request_timeout=timeouts['groups'],
        )

    def set_data(self, state, data):
        user_avatars = {}
        for user in data.get('result', []):
            user_avatars[user['id']] = user['avatar_id']

        for doc in self.get_found_docs(state):
            doc.raw_snippet['avatar_id'] = user_avatars.get(doc.raw_snippet['uid'])
