import logging
from functools import wraps

import sform

from django.conf import settings
from django.views.decorators.http import require_GET, require_http_methods

from staff.lib.db import atomic
from staff.lib.decorators import (
    available_for_external,
    responding_json,
    consuming_json,
)
from staff.lib.forms.errors import (
    sform_single_field_error,
    sform_general_error,
)
from staff.lib.utils.ordered_choices import OrderedChoices
from staff.person import models

from staff.person_profile.permissions.check_permissions import can_view_digital_sign
from staff.person_profile.permissions.utils import attach_permissions_ctl, observer_has_perm
from staff.person_profile.controllers.digital_sign import (
    attach_staff_phone,
    check_agreement_sign,
    create_digital_sign,
    get_docs_status_from_oebs,
    verify_code_by_person,
    get_digital_sign_certification_status_from_oebs,
    VERIFY_ANSWERS,
)
from staff.person_profile.controllers.phones import PhonesCtl


logger = logging.getLogger(__name__)


def _login_is_current_user(fu):
    # FIXME: every profile-api method has to have login in uri.
    # FIXME: need to be deleted after resolving STAFF-10052.
    # FIXME: Login field from urls need to be deleted as well.
    @wraps(fu)
    def wrapped(request, login, *args, **kwargs):
        if request.user.staff.login != login:
            return {'error': 'Can be used only for current user'}, 403
        return fu(request, *args, **kwargs)

    return wrapped


@available_for_external
@responding_json
@require_GET
@attach_permissions_ctl
def get(request, login):
    person = request.user.staff

    if not can_view_digital_sign(None, request.permissions_ctl.properties, login):
        return {'error': 'Can be used only for current user'}, 403

    phones = PhonesCtl(
        target_login=login,
        permissions_ctl=request.permissions_ctl,
    ).get_phones(for_digital_sign=True)
    assert len(phones) < 2, 'More than one digital sign number for user'
    return {
        'target': {
            'digital_sign': {
                'phone': phones[0] if phones else None,
                'has_signed_agreement': check_agreement_sign(person.login),
            },
        },
    }


class AttachPhoneForm(sform.SForm):
    phone = sform.SuggestField(
        queryset=models.StaffPhone.objects,
        label_fields='number',
        to_field_name='id',
        state=sform.REQUIRED,
    )


ERRS = OrderedChoices(
    ('PHONE_NOT_FOR_CUR_USER', 'phone_not_for_cur_user'),
    ('OEBS_ERROR', 'oebs_error'),
)


@available_for_external
@responding_json
@consuming_json
@require_http_methods(['GET', 'POST'])
@attach_permissions_ctl
@_login_is_current_user
def attach_phone(request, json_data):
    if request.method == 'GET':
        return AttachPhoneForm().as_dict()

    form = AttachPhoneForm(data=json_data)
    if not form.is_valid():
        return form.errors_as_dict(), 400
    phone = form.cleaned_data['phone']
    cur_person = request.user.staff
    if phone.staff != cur_person:
        return sform_single_field_error('phone', ERRS.PHONE_NOT_FOR_CUR_USER), 400

    result = attach_staff_phone(phone)
    if not result.ok:
        return sform_general_error(result.status)
    return {}


class VerifyCodeForm(sform.SForm):
    code = sform.CharField(state=sform.REQUIRED)


@available_for_external
@responding_json
@consuming_json
@require_http_methods(['GET', 'POST'])
@attach_permissions_ctl
@_login_is_current_user
def verify_code(request, json_data):
    if request.method == 'GET':
        return VerifyCodeForm().as_dict()
    form = VerifyCodeForm(data=json_data)
    if not form.is_valid():
        return form.errors_as_dict(), 400

    response_params = {}

    # Проверяем код и привязываем телефон в OeBS в случае успеха
    with atomic():
        status, code = verify_code_by_person(request.user.staff, form.cleaned_data['code'])
        if status == VERIFY_ANSWERS.OK:
            created, oebs_resp = create_digital_sign(code, request.user)
            status = status if created else VERIFY_ANSWERS.OEBS_ERROR
            if oebs_resp:
                response_params['message'] = oebs_resp

    if status != VERIFY_ANSWERS.OK:
        return sform_general_error(status, params=response_params), 400
    return {}, 200


@available_for_external
@responding_json
@require_GET
@_login_is_current_user
def get_status(request):
    try:
        oebs_response = get_docs_status_from_oebs(request.user.staff.login)
    except Exception:
        return {'error': ERRS.OEBS_ERROR}
    deadlines = (doc.deadline for proc in oebs_response.proc for doc in proc.doc)
    try:
        closest = min([_f for _f in deadlines if _f]).strftime(settings.OEBS_DATETIME_FORMAT)
    except ValueError:
        closest = None
    return {
        'target': {
            'digital_sign_status': {
                'closest_deadline': closest,
                'unsigned_status': oebs_response.unsignedDssDocCount,
            }
        }
    }


@available_for_external
@require_GET
@responding_json
@observer_has_perm('digital_sign_certification_status')
def get_certification_status(request, login):
    try:
        oebs_response = get_digital_sign_certification_status_from_oebs(login)
        return {
            'target': {
                'digital_sign_certification_status': {
                    'exists': oebs_response.existsCertificate,
                    'active': oebs_response.hasEmpCert,
                    'effective_start_date': oebs_response.period.effectiveStartDate,
                    'effective_end_date': oebs_response.period.effectiveEndDate,
                    'ticket': oebs_response.ticket,
                    'reissue_ticket': oebs_response.ticketForReissue,
                },
            },
        }
    except Exception:
        return {'error': ERRS.OEBS_ERROR}, 400
