"""API that generates auto-complete suggestions for the search bar in the header of seqr pages"""
from __future__ import unicode_literals

import logging

from django.contrib.auth.decorators import login_required
from django.db.models import Q, ExpressionWrapper, BooleanField
from django.views.decorators.http import require_GET

from reference_data.models import Omim, HumanPhenotypeOntology
from seqr.utils.gene_utils import get_queried_genes
from seqr.views.utils.json_utils import create_json_response, _to_title_case
from seqr.views.utils.permissions_utils import get_projects_user_can_view
from seqr.models import Project, Family, Individual, AnalysisGroup, ProjectCategory
from settings import API_LOGIN_REQUIRED_URL


logger = logging.getLogger(__name__)

MAX_RESULTS_PER_CATEGORY = 8
MAX_STRING_LENGTH = 100


def _get_matching_objects(query, projects, object_cls, filter_fields, object_fields, get_title, get_href, get_description=None, project_field=None, select_related_project=True):
    """Returns objects that match the given query string, and that the user can view, for the given object criteria.

    Args:
        user: Django user
        query: String typed into the awesomebar
        object_cls: Django model class of the object
        filter_fields: Array of field names to match the query against
        get_title: Function to get the title from an object
        get_href: Function to get the href from an object
        get_description: Optional function to get the description from an object
        project_field: Optional string defining the relationship between the object and parent project
    Returns:
        Sorted list of matches where each match is a dictionary of strings
    """
    if project_field:
        matching_objects = getattr(object_cls, 'objects')
        matching_objects = matching_objects.filter(Q(**{'{}__in'.format(project_field): projects}))
        if select_related_project:
            matching_objects = matching_objects.select_related(project_field)
    else:
        matching_objects = projects

    object_filter = Q()
    for field in filter_fields:
        object_filter |= Q(**{'{}__icontains'.format(field): query})
    matching_objects = matching_objects.filter(object_filter).only('guid', *object_fields).distinct()

    results = [{
        'key': obj.guid,
        'title': get_title(obj)[:MAX_STRING_LENGTH],
        'description': '({})'.format(get_description(obj)) if get_description else '',
        'href': get_href(obj),
    } for obj in matching_objects[:MAX_RESULTS_PER_CATEGORY]]

    results.sort(key=lambda f: len(f.get('title', '')))

    return results


def _get_matching_projects(query, projects):
    return _get_matching_objects(
        query, projects, Project,
        filter_fields=['name'],
        object_fields=['name'],
        get_title=lambda p: p.name,
        get_href=lambda p: '/project/{}/project_page'.format(p.guid),
    )


def _get_matching_families(query, projects):
    return _get_matching_objects(
        query, projects, Family,
        filter_fields=['family_id', 'display_name'],
        object_fields=['family_id', 'display_name', 'project__guid', 'project__name'],
        get_title=lambda f: f.display_name or f.family_id,
        get_href=lambda f: '/project/{}/family_page/{}'.format(f.project.guid, f.guid),
        get_description=lambda f: f.project.name,
        project_field='project')


def _get_matching_analysis_groups(query, projects):
    return _get_matching_objects(
        query, projects, AnalysisGroup,
        filter_fields=['name'],
        object_fields=['name', 'project__guid', 'project__name'],
        get_title=lambda f: f.name,
        get_href=lambda f: '/project/{}/analysis_group/{}'.format(f.project.guid, f.guid),
        get_description=lambda f: f.project.name,
        project_field='project')


def _get_matching_individuals(query, projects):
    return _get_matching_objects(
        query, projects, Individual,
        filter_fields=['individual_id', 'display_name'],
        object_fields=[
            'individual_id', 'display_name', 'family__guid', 'family__display_name', 'family__family_id',
            'family__project__guid', 'family__project__name',
        ],
        get_title=lambda i: i.display_name or i.individual_id,
        get_href=lambda i: '/project/{}/family_page/{}'.format(i.family.project.guid, i.family.guid),
        get_description=lambda i: '{}: family {}'.format(i.family.project.name, (i.family.display_name or i.family.family_id)),
        project_field='family__project')


def _get_matching_project_groups(query, projects):
    return _get_matching_objects(
        query, projects, ProjectCategory,
        filter_fields=['name'],
        object_fields=['name'],
        get_title=lambda p: p.name,
        get_href=lambda p: p.guid,
        project_field='projects',
        select_related_project=False,
    )


def _get_matching_genes(query, projects):
    """Returns genes that match the given query string, and that the user can view.

    Args:
       user: Django user
       query: String typed into the awesomebar
    Returns:
       Sorted list of matches where each match is a dictionary of strings
    """
    result = []
    for g in get_queried_genes(query, MAX_RESULTS_PER_CATEGORY):
        if query.lower() in g['gene_id'].lower():
            title = g['gene_id']
            description = g['gene_symbol']
        else:
            title = g['gene_symbol']
            description = g['gene_id']

        result.append({
            'key': g['gene_id'],
            'title': title,
            'description': '('+description+')' if description else '',
            'href': '/gene_info/'+g['gene_id'],
        })

    return result


def _get_matching_omim(query, projects):
    """Returns OMIM records that match the given query string"""
    records = Omim.objects.filter(
        Q(phenotype_mim_number__icontains=query) | Q(phenotype_description__icontains=query)
    ).filter(phenotype_mim_number__isnull=False).annotate(
        description_start=ExpressionWrapper(Q(phenotype_description__istartswith=query), output_field=BooleanField()),
        mim_number_start=ExpressionWrapper(Q(phenotype_mim_number__istartswith=query), output_field=BooleanField()),
    ).only('phenotype_mim_number', 'phenotype_description').order_by(
        '-description_start', '-mim_number_start', 'phenotype_description').distinct()[:MAX_RESULTS_PER_CATEGORY]
    result = []
    for record in records:
        result.append({
            'key': record.phenotype_mim_number,
            'title': record.phenotype_description,
            'description': '({})'.format(record.phenotype_mim_number) if record.phenotype_mim_number else None,
        })

    return result


def _get_matching_hpo_terms(query, projects):
    """Returns OMIM records that match the given query string"""
    records = HumanPhenotypeOntology.objects.filter(
        Q(hpo_id__icontains=query) | Q(name__icontains=query)
    ).annotate(
        name_start=ExpressionWrapper(Q(name__istartswith=query), output_field=BooleanField()),
        hpo_id_start=ExpressionWrapper(Q(hpo_id__istartswith=query), output_field=BooleanField()),
    ).only('hpo_id', 'name', 'category_id').order_by(
        '-name_start', '-hpo_id_start', 'name').distinct()[:MAX_RESULTS_PER_CATEGORY]
    result = []
    for record in records:
        result.append({
            'key': record.hpo_id,
            'title': record.name,
            'description': '({})'.format(record.hpo_id),
            'category': record.category_id,
        })

    return result


CATEGORY_MAP = {
    'genes': _get_matching_genes,
    'omim': _get_matching_omim,
    'hpo_terms': _get_matching_hpo_terms,
}
PROJECT_SPECIFIC_CATEGORY_MAP = {
    'projects': _get_matching_projects,
    'families': _get_matching_families,
    'analysis_groups': _get_matching_analysis_groups,
    'individuals': _get_matching_individuals,
    'project_groups': _get_matching_project_groups,
}
CATEGORY_MAP.update(PROJECT_SPECIFIC_CATEGORY_MAP)
DEFAULT_CATEGORIES = ['projects', 'families', 'analysis_groups', 'individuals', 'genes']


@login_required(login_url=API_LOGIN_REQUIRED_URL)
@require_GET
def awesomebar_autocomplete_handler(request):
    """Accepts HTTP GET request with q=.. url arg, and returns suggestions"""

    query = request.GET.get('q')
    if not query:
        return create_json_response({'matches': {}})

    categories = request.GET.get('categories').split(',') if request.GET.get('categories') else DEFAULT_CATEGORIES

    projects = get_projects_user_can_view(request.user) if any(
        category for category in categories if category in PROJECT_SPECIFIC_CATEGORY_MAP) else None

    results = {
        category: {'name': _to_title_case(category), 'results': CATEGORY_MAP[category](query, projects)}
        for category in categories
    }

    return create_json_response({'matches': {k: v for k, v in results.items() if v['results']}})