import json
from functools import wraps

from django.http import HttpRequest, JsonResponse, HttpResponse
from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import ensure_csrf_cookie

from staff.lib.decorators import (
    responding_json,
    make_json_response,
    make_csv_response,
    make_xlsx_response,
    use_request_lang,
    available_for_external,
    forbidden_for_robots,
    auth_by_tvm_only,
)
from staff.lib.models.mptt import filter_by_heirarchy
from staff.lib.utils.qs_values import localize

from staff.person_filter.controller import FilterNotFound
from staff.person_filter.saved_filter_ctl import SavedFilterCtl
from staff.person_filter.filter_context import FilterContext
from staff.person_profile.permissions.check_permissions import can_view_phones_in_department

from staff.departments.controllers.department import DepartmentCtl
from staff.departments.models import Department, DepartmentInterfaceSection
from staff.departments.tree.export_attendance import ExportAttendanceAccessException, ExportAttendance
from staff.departments.tree.forms import ShortEditForm, ExportAttendanceForm
from staff.departments.tree.persons_entity_info import PersonsEntityInfo
from staff.departments.tree.persons_export import PersonsExport, ExportForm
from staff.departments.tree.persons_list_filler import PersonsListFiller
from staff.departments.tree.vcards_mailto import get_vcards, get_mailto
from staff.departments.tree_lib import Pager, TreeBuilder, TreeExpander, TreeGetter


def with_department_filter_context(view):
    @wraps(view)
    def wrapper(request: HttpRequest, *args, **kwargs):
        observer_tz = request.user.get_profile().tz
        filter_id = request.GET.get('filter')
        try:
            filter_context = FilterContext(
                filter_id=filter_id,
                observer_tz=observer_tz,
                user=request.user,
                permission='django_intranet_stuff.can_view_departments',
            )
            return view(request, filter_context, *args, **kwargs)
        except FilterNotFound:
            return {'error': 'filter_not_found'}, 404

    return wrapper


def _department_not_found(url: str):
    return HttpResponseNotFound('Department "%s" not found' % url)


@responding_json
@require_http_methods(['GET'])
@use_request_lang
@available_for_external
@forbidden_for_robots
@with_department_filter_context
def tree(request: HttpRequest, filter_context: FilterContext, url=None):
    MIN_QUERY_CHARS = 4

    search_q = request.GET.get('q', '').strip()
    info_provider = PersonsEntityInfo(filter_context, request.user.get_profile())
    tree_getter = TreeGetter(info_provider)

    status = 200
    extra = {}

    if search_q is None or len(search_q) < MIN_QUERY_CHARS:
        if url is None:
            departments = tree_getter.get_default()
        else:
            departments = tree_getter.get_by_url(url)
    elif len(search_q) < MIN_QUERY_CHARS:
        departments, extra = [], {'error': 'short_q'}
        status = 400
    else:
        departments, extra = tree_getter.search(search_q)

    if not departments and status == 200:
        status = 400
        if extra.get('relevant_qty'):
            extra['error'] = 'too_many_results'
        else:
            extra['error'] = 'nothing_found'

    result = {
        'tree': TreeBuilder(info_provider).get_as_short_tree(departments)
    }
    result.update(extra)
    return result, status


@responding_json
@require_http_methods(['GET'])
@use_request_lang
@available_for_external
@forbidden_for_robots
@with_department_filter_context
def tree_info(request: HttpRequest, filter_context: FilterContext, url):
    try:
        target = filter_context.get_base_dep_qs().get(url=url)
    except Department.DoesNotExist:
        return _department_not_found(url)

    target_id = target['id']

    persons_map = {target_id: PersonsListFiller(filter_context).get_as_list(
        filter_context
        .get_base_person_qs()
        .filter(department_id=target_id)
    )}

    info_provider = PersonsEntityInfo(filter_context, request.user.get_profile())

    return TreeBuilder(info_provider).get_as_list([target], persons_map)[0]


@responding_json
@require_http_methods(['GET'])
@use_request_lang
@available_for_external
@forbidden_for_robots
@with_department_filter_context
def tree_descendants(request: HttpRequest, filter_context: FilterContext, url):
    descendants_qs = (
        filter_context.get_base_dep_qs()
        .filter(parent__url=url)
        .order_by('tree_id', 'lft')
    )

    info_provider = PersonsEntityInfo(filter_context, request.user.get_profile())

    return {
        'descendants': TreeBuilder(info_provider).get_as_short_list(descendants_qs)
    }


@responding_json
@require_http_methods(['GET'])
@use_request_lang
@available_for_external
@forbidden_for_robots
@with_department_filter_context
def tree_expand(request: HttpRequest, filter_context: FilterContext, url):
    try:
        persons_info = PersonsEntityInfo(filter_context, request.user.get_profile())
        return TreeExpander(persons_info, url).expand()
    except Department.DoesNotExist:
        return _department_not_found(url)


@ensure_csrf_cookie
@responding_json
@require_http_methods(['GET'])
@use_request_lang
@forbidden_for_robots
@with_department_filter_context
def persons(request: HttpRequest, filter_context: FilterContext, url=None):
    skip = int(request.GET.get('skip', 0))

    try:
        filler = PersonsEntityInfo(filter_context)
        pager = Pager(filler, url, skip=skip)
        departments, continuation_token = pager.get_grouped_entities()
    except Department.DoesNotExist:
        return _department_not_found(url)

    result = {'departments': departments}

    if continuation_token:
        result['next_skip'] = continuation_token

    if skip == 0:
        result['persons_count'] = pager.count()

        if pager.department:
            result['url_department_name'] = localize(pager.department)['name']

        saved_filter = (
            SavedFilterCtl(request.user.get_profile())
            .get_by_department_filter_id(
                department_url=url,
                filter_id=filter_context.filter_id,
            )
        )
        if saved_filter:
            result['saved_filter'] = saved_filter

    return result


@ensure_csrf_cookie
@responding_json
@require_http_methods(['GET'])
@use_request_lang
@forbidden_for_robots
def export_meta(request: HttpRequest):
    return PersonsExport.meta()


@responding_json
@require_http_methods(['GET'])
@use_request_lang
@available_for_external
@forbidden_for_robots
def tree_sections(request: HttpRequest) -> JsonResponse:
    fields = 'id', 'name', 'name_en', 'order', 'description'
    sections = DepartmentInterfaceSection.objects.values(*fields)
    sections = {'sections': {s.pop('id'): localize(s) for s in sections}}
    return JsonResponse(sections)


@require_http_methods(['GET'])
@use_request_lang
@responding_json
@forbidden_for_robots
@with_department_filter_context
def export(request: HttpRequest, filter_context: FilterContext, url=None):
    form = ExportForm(data=request.GET)
    file_name = 'persons'

    if form.is_valid():
        try:
            data = PersonsExport(filter_context, url, form).export()
            if form.cleaned_data['data_format'] == 'xlsx':
                return make_xlsx_response(file_name=None, prefix=file_name, data=[[file_name, data]])
            else:
                return make_csv_response(file_name, data)
        except Department.DoesNotExist:
            return _department_not_found(url)
    else:
        return make_json_response(request, form.errors)


@require_http_methods(['GET'])
@forbidden_for_robots
@with_department_filter_context
def export_attendance(request: HttpRequest, filter_context: FilterContext) -> JsonResponse:
    form = ExportAttendanceForm(data=request.GET)
    if not form.is_valid():
        return JsonResponse(form.errors, status=400)
    try:
        queryset = filter_context.get_person_obj_qs()
        department = form.cleaned_data['department']
        if department is not None:
            queryset = filter_by_heirarchy(
                queryset,
                [department],
                by_children=True,
                include_self=True,
                filter_prefix='department__',
            )
        return JsonResponse(
            data=ExportAttendance(queryset, form.cleaned_data['date_from'], form.cleaned_data['date_to']).export(),
            status=200,
        )
    except ExportAttendanceAccessException as e:
        return JsonResponse({'errors': {e.field: [{'code': e.message}]}}, status=400)


@require_http_methods(['GET'])
@use_request_lang
@forbidden_for_robots
@with_department_filter_context
def vcards(request: HttpRequest, filter_context: FilterContext, url=None):
    try:
        return get_vcards(filter_context, can_view_phones_in_department(request.user.get_profile(), url), url)
    except Department.DoesNotExist:
        return _department_not_found(url)


@require_http_methods(['GET'])
@use_request_lang
@available_for_external
@forbidden_for_robots
@with_department_filter_context
def mailto(request: HttpRequest, filter_context: FilterContext, url=None):
    try:
        return get_mailto(filter_context, url)
    except Department.DoesNotExist:
        return _department_not_found(url)


@require_http_methods(['GET', 'POST'])
@forbidden_for_robots
@responding_json
def short_edit(request: HttpRequest, url):
    try:
        initial = (
            Department.objects
            .values(
                'description',
                'description_en',
                'wiki_page',
                'clubs',
                'maillists',
            )
            .get(url=url)
        )
    except Department.DoesNotExist:
        return _department_not_found(url)

    data = {}
    if request.method == 'POST':
        try:
            data = json.loads(request.read())
        except ValueError:
            HttpResponseBadRequest('invalid_json')

    form = ShortEditForm(initial=initial, data=data)

    if request.method == 'POST':
        if form.is_valid():
            dep = Department.objects.get(url=url)
            ctl = DepartmentCtl(dep, author_user=request.user)
            for field_name, field_value in form.cleaned_data.items():
                setattr(ctl, field_name, field_value)
            ctl.save()
            return {'success': True}
        else:
            return form.errors
    else:
        return form.as_dict()


@responding_json
@require_http_methods(['GET'])
@forbidden_for_robots
@use_request_lang
def ancestors(request: HttpRequest, url):
    try:
        # .objects will return only Department, but not VS
        department_or_valuestream = Department.all_types.get(url=url)
    except Department.DoesNotExist:
        return {'errors': ['not_found']}, 404

    ancestors_ = filter_by_heirarchy(
        Department.all_types.all(),
        mptt_objects=[department_or_valuestream],
        by_children=False,
        include_self=True,
    ).values('url', 'name', 'name_en', 'id')

    ancestors_ = [localize(ancestor) for ancestor in ancestors_]

    return ancestors_


@auth_by_tvm_only(['femida'])
def value_streams(request: HttpRequest) -> HttpResponse:
    departments = Department.valuestreams.prefetch_related('hrproduct_set')

    result = []
    for department in departments:
        hr_product = department.hrproduct_set.first()
        result.append({
            'id': department.id,
            'name': department.name,
            'name_en': department.name_en,
            'slug': department.url[len('svc_'):],
            'oebs_product_id': hr_product.id if hr_product else None,
            'st_translation_id': hr_product.st_translation_id if hr_product else None,
            'is_active': bool(department.intranet_status),
        })

    return JsonResponse({'result': result}, status=200)
