# -*- coding: utf-8 -*-
from collections import defaultdict, OrderedDict
from django.conf import settings
from django.utils.translation import ugettext as _
from events.surveyme.models import (
    SurveyQuestion,
    SurveyQuestionChoice,
    SurveyQuestionMatrixTitle,
)
from events.countme.models import (
    AnswerCount,
    QuestionCount,
)
from events.staff.models import (
    StaffGroup,
    StaffOffice,
    StaffOrganization,
    StaffPerson,
)
from events.abc.models import AbcService
from events.accounts.utils import GENDER_MALE, GENDER_FEMALE
from events.common_app.directory import CachedDirectoryClient
from events.common_app.wiki import get_wiki_client
from events.data_sources.models import TableRow, University
from events.geobase_contrib.models import City, Country
from events.music.models import MusicGenre


class IntegerKeysMixin:
    def get_keys(self, source_keys):
        keys = []
        for key in source_keys:
            try:
                keys.append(int(key))
            except (ValueError, TypeError):
                pass
        return keys


class DataSourceStaffOffice(IntegerKeysMixin):
    data_source = 'staff_office'

    def get_data(self, keys, **kwargs):
        return {
            str(office.staff_id): office.get_name()
            for office in (
                StaffOffice.objects.using(settings.DATABASE_ROLOCAL)
                .filter(staff_id__in=self.get_keys(keys))
            )
        }


class DataSourceStaffLogin:
    data_source = 'staff_login'

    def get_data(self, keys, **kwargs):
        return {
            str(person.login): '%s %s' % (person.get_first_name(), person.get_last_name())
            for person in (
                StaffPerson.objects.using(settings.DATABASE_ROLOCAL)
                .filter(login__in=keys)
            )
        }


class DataSourceStaffGroup(IntegerKeysMixin):
    data_source = 'staff_group'

    def get_data(self, keys, **kwargs):
        return {
            str(group.staff_id): group.name
            for group in (
                StaffGroup.objects.using(settings.DATABASE_ROLOCAL)
                .filter(staff_id__in=self.get_keys(keys))
            )
        }


class DataSourceStaffOrganization(IntegerKeysMixin):
    data_source = 'staff_organization'

    def get_data(self, keys, **kwargs):
        return {
            str(org.staff_id): org.name
            for org in (
                StaffOrganization.objects.using(settings.DATABASE_ROLOCAL)
                .filter(staff_id__in=self.get_keys(keys))
            )
        }


class DataSourceUniversity:
    data_source = 'university'

    def get_data(self, keys, **kwargs):
        return {
            str(uni.pk): uni.name
            for uni in (
                University.objects.using(settings.DATABASE_ROLOCAL)
                .filter(pk__in=keys)
            )
        }


class DataSourceGender:
    data_source = 'gender'

    def get_data(self, keys, **kwargs):
        return {
            GENDER_MALE: _('Мужской'),
            GENDER_FEMALE: _('Женский'),
        }


class DataSourceWikiTableSource:
    data_source = 'wiki_table_source'

    def get_supertag(self, params, dir_id):
        filters = params.get('filters')
        if not filters:
            return None

        page_url = filters[0]['value']
        if not page_url:
            return None

        client = get_wiki_client(dir_id)
        return client.get_supertag(page_url)

    def get_table_data(self, supertag, user_uid, dir_id):
        client = get_wiki_client(dir_id)
        try:
            return client.get_table_data(supertag)
        except Exception:
            return {}

    def get_name_column(self, table_data):
        for i, field in enumerate(table_data['structure']['fields']):
            if field['title'] == 'name':
                return i

    def get_data(self, keys, **kwargs):
        params = kwargs.get('params', {})
        user_uid = kwargs.get('user_uid')
        dir_id = kwargs.get('dir_id')

        supertag = self.get_supertag(params, dir_id)
        if not supertag:
            return {}

        table_data = self.get_table_data(supertag, user_uid, dir_id)
        if not table_data:
            return {}

        name_column = self.get_name_column(table_data)
        if name_column is None:
            return {}

        return {
            str(i): row[name_column]['raw']
            for i, row in enumerate(table_data.get('rows'), start=1)
            if str(i) in keys
        }


class DataSourceYtTableSource:
    data_source = 'yt_table_source'

    def get_table_identifier(self, params):
        from events.data_sources.utils import get_table_name, get_table_id
        filters = params.get('filters')
        if not filters:
            return None

        claster_name, table_path = get_table_name(filters[0].get('value', ''))
        if not claster_name or not table_path:
            return None

        return get_table_id(claster_name, table_path)

    def get_data(self, keys, **kwargs):
        params = kwargs.get('params', {})
        table_identifier = self.get_table_identifier(params)
        if not table_identifier:
            return {}
        return {
            str(row.source_id): row.text
            for row in (
                TableRow.objects.using(settings.DATABASE_ROLOCAL)
                .filter(table_identifier=table_identifier, source_id__in=keys)
            )
        }


class DataSourceAbcService:
    data_source = 'abc_service'

    def get_data(self, keys, **kwargs):
        return {
            str(service.slug): service.get_name()
            for service in (
                AbcService.objects.using(settings.DATABASE_ROLOCAL)
                .filter(slug__in=keys)
            )
        }


class DataSourceCity:
    data_source = 'city'

    def get_data(self, keys, **kwargs):
        return {
            str(city.pk): city.get_name()
            for city in (
                City.objects.using(settings.DATABASE_ROLOCAL)
                .filter(pk__in=keys)
            )
        }


class DataSourceCountry:
    data_source = 'country'

    def get_data(self, keys, **kwargs):
        return {
            str(country.pk): country.get_name()
            for country in (
                Country.objects.using(settings.DATABASE_ROLOCAL)
                .filter(pk__in=keys)
            )
        }


class DataSourceMusicGenre:
    data_source = 'music_genre'

    def get_data(self, keys, **kwargs):
        return {
            str(mg.music_id): mg.get_title()
            for mg in (
                MusicGenre.objects.using(settings.DATABASE_ROLOCAL)
                .filter(music_id__in=keys)
            )
        }


class DataSourceDirUser:
    data_source = 'dir_user'

    def _get_name(self, user):
        name = user.get('name', {})
        return '%s %s' % (name.get('first', ''), name.get('last', ''))

    def get_data(self, keys, **kwargs):
        client = CachedDirectoryClient()
        dir_id = kwargs.get('dir_id')
        sorted_keys = set(keys)
        return {
            str(it['id']): self._get_name(it)
            for it in client.get_users(dir_id, fields='id,name,is_robot') or []
            if not it['is_robot'] and str(it['id']) in sorted_keys
        }


class DataSourceDirDepartment:
    data_source = 'dir_department'

    def _get_name(self, department):
        return department.get('name')

    def get_data(self, keys, **kwargs):
        client = CachedDirectoryClient()
        dir_id = kwargs.get('dir_id')
        sorted_keys = set(keys)
        return {
            str(it['id']): self._get_name(it)
            for it in client.get_departments(dir_id, fields='id,name') or []
            if str(it['id']) in sorted_keys
        }


class DataSourceDirGroup:
    data_source = 'dir_group'

    def _get_name(self, group):
        return group.get('name')

    def get_data(self, keys, **kwargs):
        client = CachedDirectoryClient()
        dir_id = kwargs.get('dir_id')
        sorted_keys = set(keys)
        return {
            str(it['id']): self._get_name(it)
            for it in client.get_groups(dir_id, fields='id,name') or []
            if str(it['id']) in sorted_keys
        }


data_sources = [
    DataSourceStaffOffice,
    DataSourceStaffLogin,
    DataSourceYtTableSource,
    DataSourceWikiTableSource,
    DataSourceUniversity,
    DataSourceGender,
    DataSourceAbcService,
    DataSourceCity,
    DataSourceCountry,
    DataSourceStaffOrganization,
    DataSourceStaffGroup,
    DataSourceMusicGenre,
    DataSourceDirUser,
    DataSourceDirDepartment,
    DataSourceDirGroup,
]

data_sources_dict = {
    it.data_source: it()
    for it in data_sources
}


def get_data(data_source, keys, **kwargs):
    ds = data_sources_dict.get(data_source)
    if not ds:
        return {}
    return ds.get_data(keys, **kwargs)


class Stats:
    def __init__(self, survey):
        self.survey = survey
        self.questions = OrderedDict()
        self.data_sources = set()
        self.choices = defaultdict(OrderedDict)
        self.titles = defaultdict(lambda: (OrderedDict(), OrderedDict()))
        self._init_metadata()

    def _init_metadata(self):
        self._init_questions()
        self._init_choices()
        self._init_titles()

    def _init_questions(self):
        question_qs = (
            SurveyQuestion.objects.using(settings.DATABASE_ROLOCAL)
            .filter(survey_id=self.survey.pk)
            .select_related('answer_type')
            .order_by('page', 'position')
        )
        grouped = defaultdict(list)
        not_grouped = []
        for question in question_qs:
            self.data_sources.add(question.param_data_source)
            if question.group_id:
                grouped[question.group_id].append(question)
            else:
                not_grouped.append(question)
        for question in not_grouped:
            if question.answer_type.slug == 'answer_group':
                for child in grouped.get(question.pk) or []:
                    self.questions[child.pk] = child
            elif not question.answer_type.is_read_only:
                self.questions[question.pk] = question

    def _init_choices(self):
        if 'survey_question_choice' in self.data_sources:
            choice_qs = (
                SurveyQuestionChoice.objects.using(settings.DATABASE_ROLOCAL)
                .filter(survey_question__survey_id=self.survey.pk)
                .order_by('position')
            )
            for choice in choice_qs:
                self.choices[choice.survey_question_id][str(choice.pk)] = choice

    def _init_titles(self):
        if 'survey_question_matrix_choice' in self.data_sources:
            title_qs = (
                SurveyQuestionMatrixTitle.objects.using(settings.DATABASE_ROLOCAL)
                .filter(survey_question__survey_id=self.survey.pk)
                .order_by('position')
            )
            for title in title_qs:
                if title.type == 'row':
                    self.titles[title.survey_question_id][0][str(title.pk)] = title
                elif title.type == 'column':
                    self.titles[title.survey_question_id][1][str(title.pk)] = title

    def get_total(self):
        try:
            ac = (
                AnswerCount.objects.using(settings.DATABASE_ROLOCAL)
                .get(survey_id=self.survey.pk)
            )
            return ac.count
        except AnswerCount.DoesNotExist:
            return 0

    def get_question_count(self):
        question_count_qs = (
            QuestionCount.objects.using(settings.DATABASE_ROLOCAL)
            .filter(survey_id=self.survey.pk)
        )
        counter = defaultdict(dict)
        for qs in question_count_qs:
            counter[qs.question_id][qs.composite_key] = qs.count
        return counter

    def get_data_source_stats(self, question, counter):
        if not counter.keys():
            return
        kwargs = {
            'params': question.param_data_source_params,
            'user_uid': self.survey.user.uid,
            'dir_id': getattr(self.survey.org, 'dir_id', None),
        }
        data = get_data(question.param_data_source, counter.keys(), **kwargs)
        for composite_key, count in counter.items():
            yield data.get(composite_key, composite_key), count

    def sort_items(self, stats):
        sort_key = lambda it: it[1]
        return sorted(stats, key=sort_key, reverse=True)

    def get_question_stats(self, question, counter):
        slug = question.answer_type.slug
        if slug == 'answer_boolean':
            yield _('Да'), counter.get('1', 0)
            yield _('Нет'), counter.get('0', 0)
        elif slug == 'answer_choices':
            data_source = question.param_data_source
            if data_source == 'survey_question_choice':
                stats = (
                    (choice.get_label(), counter.get(str(choice.pk), 0))
                    for choice in self.choices[question.pk].values()
                )
                for label, count in self.sort_items(stats):
                    yield label, count
            elif data_source == 'survey_question_matrix_choice':
                rows, cols = self.titles[question.pk]
                for row in rows.values():
                    stats = (
                        (col.get_label(), counter.get('%s:%s' % (row.pk, col.pk), 0))
                        for col in cols.values()
                    )
                    yield row.get_label(), [
                        {
                            'label': label,
                            'count': count,
                        }
                        for label, count in self.sort_items(stats)
                    ]
            else:
                stats = self.get_data_source_stats(question, counter)
                for label, count in self.sort_items(stats):
                    yield label, count

    def get_questions(self, page=None, page_size=None):
        page = page or 1
        full_counter = self.get_question_count()
        questions = list(self.questions.values())
        if page_size:
            questions = questions[(page - 1) * page_size:page * page_size]
        for question in questions:
            counter = full_counter.get(question.pk, {})
            info = {
                'answers': sum(counter.values()),
                'label': question.get_label(),
                'question': {
                    'id': question.pk,
                    'group_id': None,
                    'is_hidden': question.param_is_hidden,
                },
            }
            stats = OrderedDict(self.get_question_stats(question, counter))
            info['stats'] = stats
            info['detailed_stats'] = stats
            yield info

    def get_info(self, page=None, page_size=None):
        page = page or 1
        return {
            'answers': {
                'count': self.get_total(),
            },
            'questions': list(self.get_questions(page=page, page_size=page_size)),
        }


def get_stats_info(survey, page=None, page_size=None):
    stats = Stats(survey)
    return stats.get_info(page=page, page_size=page_size)
