import re
from itertools import chain

from bson.objectid import ObjectId
from django.db.models import Q
from django.conf import settings
from django.contrib.auth.decorators import permission_required
from django.shortcuts import render
from django.views.decorators.http import require_http_methods

from staff.person.models import Staff
from staff.lib.decorators import responding_json

from staff.departments.controllers.proposal_execution import ProposalExecution
from staff.departments.controllers.proposal import (
    ProposalCtl,
    collect_department_ids,
    collect_person_ids_from_proposal
)
from staff.departments.controllers.proposal_action import (
    is_creating,
    is_data_modification,
    is_deleting, is_moving
)
from staff.departments.edit.proposal_mongo import (
    from_mongo_id,
    get_mongo_object,
    get_mongo_objects,
    utc_to_local,
    to_mongo_id,
)
from staff.departments.models import Department, ProposalMetadata
from staff.proposal.controllers import ProposalTasks
from staff.proposal import tasks


def get_action_type(act):
    if is_deleting(act):
        return 'delete'
    if is_creating(act):
        return 'create'
    if is_moving(act):
        return 'move'
    if is_data_modification(act):
        return 'data'


@permission_required('django_intranet_stuff.can_watch_department_proposals')
@require_http_methods(['GET'])
def proposal_list(request):
    first_page = 1
    default_limit = 200

    sort = request.GET.getlist('sort')
    params = dict.fromkeys(('author', 'created_at'), {'$exists': True})
    params.update(request.GET.dict())

    search_param = params.pop('query', None)
    if search_param:
        search_param = search_param.strip().lower()
        # список id всех сотрудников, у которых в логине содержится search_param
        staffs = list(Staff.objects.filter(login__icontains=search_param).values_list('id', flat=True))
        # список id всех подразделений, у которых в любом(рус, анг) имени содержится search_param
        departments = list(Department.objects.filter(
            Q(name__icontains=search_param) |
            Q(name_en__icontains=search_param) |
            Q(url__icontains=search_param))
            .values_list('id', flat=True))

        # для поиска вхождения без учета регистра
        pattern = re.compile(search_param, re.IGNORECASE)
        # proposal filter criteria
        params['$or'] = [
            {'actions.administration.chief': {'$in': staffs}},  # filter by department chief login
            {'actions.administration.deputies': {'$in': staffs}},  # filter by department deputy login
            {'author': {'$in': staffs}},  # filter by author's login of proposal
            {'actions.id': {'$in': departments}},  # filter by department name
            {'actions.hierarchy.parent': {'$in': departments}},  # filter by parent department name
            {'actions.name.name_en': pattern},  # filter by new department name
            {'actions.name.name': pattern},
            {'actions.name.short_name': pattern},
            {'actions.name.short_name_en': pattern},
        ]
        # if search param is valid proposal id (mongo object), then filter by it too
        if ObjectId.is_valid(search_param):
            params['$or'].append({'_id': ObjectId(search_param)})

    params.pop('sort', None)

    if 'applied' in params:
        if params.pop('applied').lower() == 'true':
            proposal_ids = [
                to_mongo_id(_id)
                for _id in ProposalMetadata.objects
                .exclude(applied_at=None)
                .values_list('proposal_id', flat=True)
            ]
        else:
            proposal_ids = [
                to_mongo_id(_id)
                for _id in ProposalMetadata.objects
                .filter(applied_at=None, deleted_at=None)
                .values_list('proposal_id', flat=True)
            ]

        params['_id'] = {'$in': proposal_ids}

    page = int(params.pop('page', first_page))
    page = page if page > 0 else first_page
    limit = int(params.pop('limit', default_limit))

    proposals = list(
        get_mongo_objects(
            spec=params,
            sort=sort or ['-created_at'],
            skip=(page - 1) * limit,
            limit=limit,
        )
    )
    for proposal in proposals:
        proposal['_id'] = from_mongo_id(proposal['_id'])

    correct_proposal_ids = set(
        ProposalMetadata.objects
        .filter(proposal_id__in=[p['_id'] for p in proposals])
        .values_list('proposal_id', flat=True)
    )
    proposals = [p for p in proposals if p['_id'] in correct_proposal_ids]

    persons = dict(
        Staff.objects
        .filter(id__in=chain.from_iterable(collect_person_ids_from_proposal(p) for p in proposals))
        .values_list('id', 'login')
    )

    departments = dict(
        Department.objects
        .filter(id__in=chain.from_iterable(collect_department_ids(p) for p in proposals))
        .values_list('id', 'url')
    )

    proposals_meta_values_qs = (
        ProposalMetadata.objects
        .filter(proposal_id__in=[from_mongo_id(p['_id']) for p in proposals])
        .values_list('proposal_id', 'applied_at', 'applied_by__login', 'pushed_to_oebs')
    )

    applied_data = {
        prop_id: (applied_at, applied_by, pushed_to_oebs)
        for prop_id, applied_at, applied_by, pushed_to_oebs in proposals_meta_values_qs
    }

    proposals = (
        {
            'id': p['_id'],
            'author': persons[p['author']],
            'notify': [persons[np] for np in p.get('notify', [])],
            'created_at': utc_to_local(p['created_at']),
            'applied_at': utc_to_local(applied_data[p['_id']][0]),
            'applied_by': applied_data[p['_id']][1],
            'department_ticket': (
                p['tickets'].get('department_ticket')
                or p['tickets'].get('department_linked_ticket')
            ),
            'locked': p.get('locked', False),
            'root_departments': p.get('root_departments'),
            'departments': [
                {
                    'url': departments.get(act['id'], act['id']),
                    'action_type': get_action_type(act),
                }
                for act in p['actions']
            ],
            'error': p.get('last_error'),
            'pushed_to_oebs': utc_to_local(applied_data[p['_id']][2]),
        } for p in proposals
    )

    proposal_count = ProposalCtl.get_proposals_count(spec=params)
    num_pages = proposal_count // limit + (1 if proposal_count % limit else 0)
    return render(
        request,
        'admin_proposal_list.html',
        {
            'proposals': proposals,
            'st_host': settings.STARTREK_URL,
            'count': proposal_count,
            'page': page,
            'pages': list(range(first_page, num_pages + first_page)),
            'can_execute_role': request.user.has_perm('django_intranet_stuff.can_execute_department_proposals')
        }
    )


@permission_required('django_intranet_stuff.can_watch_department_proposals')
@require_http_methods(['GET'])
@responding_json
def proposal_data(request, proposal_id):
    object = get_mongo_object(proposal_id)
    object['_id'] = from_mongo_id(object.pop('_id'))
    return object


@permission_required('django_intranet_stuff.can_execute_department_proposals')
@require_http_methods(['POST'])
@responding_json
def execute_proposal(request, proposal_id):
    ctl = ProposalExecution(ProposalCtl(proposal_id))
    ctl.execute(execution_author_login=request.user.get_profile().login)
    return {'result': True}


@permission_required('django_intranet_stuff.can_execute_department_proposals')
@require_http_methods(['POST'])
@responding_json
def unlock_proposal(request, proposal_id):
    ctl = ProposalCtl(proposal_id)
    ctl.rollback_actions_state(error_message='Unlock pressed by {login}'.format(login=request.user.username))
    ctl.unlock()
    return {'result': True}


@permission_required('django_intranet_stuff.can_execute_department_proposals')
@require_http_methods(['POST'])
@responding_json
def delete_proposal(request, proposal_id):
    person = request.user.get_profile()

    proposal_metadata = ProposalMetadata.objects.get(proposal_id=proposal_id)
    ProposalTasks.schedule_ordered_task(
        proposal_metadata.id,
        callable_or_task=tasks.delete_proposal_task,
        kwargs={'login': person.login, 'proposal_id': proposal_metadata.id}
    )

    return {'result': ProposalCtl(proposal_id).delete() is None}
