import logging

import blackbox
from django.conf import settings
from django_yauth.util import get_real_ip

from .sessions import User, set_session
from .decorators import rpc_method
from . import RpcException


log = logging.getLogger(__name__)

blackbox.BLACKBOX_URL = settings.BLACKBOX_URL


def _get_passport_fields():
    """Возвращает набор паспортных полей из настройки PASSPORT_FIELDS.
    Совместима с аналогичной настройкой из django_yauth
    """
    defaults = [blackbox.FIELD_LANGUAGE, blackbox.FIELD_LOGIN]
    fields = getattr(settings, 'PASSPORT_FIELDS', []) or []

    # замена констант на пару (поле, алиас)
    fields = [getattr(blackbox, f) if isinstance(f, str) else f for f in fields]

    return list(set(defaults + fields))


class AuthenticationFailed(RpcException):
    def __init__(self):
        RpcException.__init__(self, 'Username and/or password is incorrect')


@rpc_method(name='login', module='rpc')
def login(login='', passwd='', **kwargs):
    """
    Login to rpc server

    @param login: user login
    @type login: string

    @param passwd: user password
    @type passwd: string

    @return: authorization token
    @rtype: string

    @raise AuthenticationFailed: when login and password incorrect
    """

    request = kwargs['request']
    user = None

    if not (login and passwd):
        user = auth_by_session_id(request=request)
        log.debug('auth_by_session_id was used')

    else:
        if not getattr(settings, 'DJANGO_RPC_DISABLE_BB_LOGIN', False):
            user = auth_by_passport_credentials(login, passwd, request)

        if user is None:
            user = auth_by_django_credentials(login, passwd)
            log.debug('auth_by_django_credentials was used')
        else:
            log.debug('auth_by_passport_credentials was used')

    if user is None:
        log.info('XML RPC Authentication failed')
        raise AuthenticationFailed()

    session = set_session(user)

    return session['token']


def auth_by_session_id(request):
    session_id = request.COOKIES.get('Session_id')
    user_ip = get_real_ip(request)

    if session_id is None:
        return

    host = request.get_host()
    port = str(request.META.get('SERVER_PORT', ''))
    host = host.replace(':' + port, '')

    try:
        resp = blackbox.sessionid(
            sessionid=session_id,
            userip=user_ip,
            host=host,
            dbfields=_get_passport_fields()
        )
    except blackbox.BlackboxError:
        log.exception('Blackbox sessionid query failed')
        return

    return _get_user_by_blackbox_response(resp, auth_type='sessionid')


def auth_by_passport_credentials(login, password, request):
    try:
        resp = blackbox.login(
            login=login,
            password=password,
            userip=get_real_ip(request),
            dbfields=_get_passport_fields(),
            captcha='yes'
        )
    except blackbox.BlackboxError:
        log.exception('Blackbox login query failed')
        return

    return _get_user_by_blackbox_response(
        resp, auth_type='passport credentials')


def auth_by_django_credentials(login, password):
    """
    Django native auth (for gobots)
    """
    user = _get_user_by_login(login)
    if user and not user.check_password(password):
        log.info('Auth by django credentials failed')
        return

    return user


def _get_user_by_blackbox_response(resp, auth_type='unknown'):
    # логгируем все что можно для возможных разбирательств
    user = None
    bb_valid_statuses = ('VALID', 'NEED_RESET')

    if not resp:
        log.info('Blackbox session is blank')
    elif resp.status not in bb_valid_statuses:
        log.info('Blackbox session is not valid')
    elif not resp.fields:
        log.info('Blackbox session fields is blank')
    elif 'login' not in resp.fields:
        log.info('Blackbox session has no login key')
    else:
        user = _get_user_by_login(login=resp.fields['login'])

    if user is None:
        log.info('Auth by %s failed. Blackbox response: %s', auth_type, resp)
    return user


def _get_user_by_login(login):
    try:
        return User.objects.get(username=login)
    except User.DoesNotExist:
        log.info('User `%s` not found in database', login)
