# -*- coding: utf-8 -*-
import datetime
import dateutil
import logging

from collections import defaultdict
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

from events.common_app.utils import lazy_re_compile
from events.surveyme.models import (
    Survey,
    SurveyQuestion,
    SurveyQuestionChoice,
    SurveyQuestionMatrixTitle,
    SurveyText,
)
from events.tanker.client import TankerClient
from events.tanker.models import TankerKeyset

logger = logging.getLogger(__name__)

APPROVED = 'APPROVED'
TRANSLATED = 'TRANSLATED'
EXPIRED = 'EXPIRED'
REQUIRES_TRANSLATION = 'REQUIRES_TRANSLATION'

PRIORITIES = {
    APPROVED: 8,
    TRANSLATED: 4,
    EXPIRED: 2,
    REQUIRES_TRANSLATION: 1,
}

STATUSES = {
    APPROVED: _('APPROVED'),
    TRANSLATED: _('TRANSLATED'),
    EXPIRED: _('EXPIRED'),
    REQUIRES_TRANSLATION: _('REQUIRES_TRANSLATION'),
}

_key_re = lazy_re_compile(r'([^.]+)\.([^.]+)\.([^.]+)\.(.+)')


def _generate_keys(model_class, queryset):
    ct = ContentType.objects.get_for_model(model_class)
    for row in queryset:
        pk = row.pop('pk')
        for name, value in row.items():
            if value:
                yield (f'{ct.app_label}.{ct.model}.{pk}.{name}', value)


def _get_actual_keys(survey_id):
    keys = []
    queryset = (
        Survey.objects.using(settings.DATABASE_ROLOCAL)
        .values('pk', 'name')
        .filter(pk=survey_id)
    )
    keys.extend(_generate_keys(Survey, queryset))

    queryset = (
        SurveyQuestion.objects.using(settings.DATABASE_ROLOCAL)
        .values('pk', 'label',  'param_help_text')
        .filter(survey_id=survey_id)
    )
    keys.extend(_generate_keys(SurveyQuestion, queryset))

    queryset = (
        SurveyQuestionChoice.objects.using(settings.DATABASE_ROLOCAL)
        .values('pk', 'label')
        .filter(survey_question__survey_id=survey_id)
    )
    keys.extend(_generate_keys(SurveyQuestionChoice, queryset))

    queryset = (
        SurveyQuestionMatrixTitle.objects.using(settings.DATABASE_ROLOCAL)
        .values('pk', 'label')
        .filter(survey_question__survey_id=survey_id)
    )
    keys.extend(_generate_keys(SurveyQuestionMatrixTitle, queryset))

    queryset = (
        SurveyText.objects.using(settings.DATABASE_ROLOCAL)
        .values('pk', 'value')
        .filter(survey_id=survey_id)
        .exclude(value='')
    )
    keys.extend(_generate_keys(SurveyText, queryset))
    return dict(keys)


def create_or_modify_keyset(client, keyset, languages):
    keyset_data = client.get_keyset(keyset)
    if not keyset_data:
        keyset_data = client.create_keyset(keyset, languages=languages)
    else:
        known_languages = set(lang.lower() for lang in keyset_data['meta']['languages'])
        if known_languages != set(languages) or keyset_data['status'] == 'ARCHIVED':
            keyset_data = client.change_keyset(keyset, languages=languages)
    return keyset_data


def _get_localized_value(key, language):
    translations = {
        lang.lower(): translation
        for lang, translation in key['translations'].items()
    }
    return translations.get(language, {}).get('payload', {}).get('singular_form', '')


def create_or_modify_keys(client, keyset, actual_keys, original_language=None):
    original_language = original_language or 'ru'
    known_keys_data = client.get_keys(keyset)
    known_keys = {
        key['name']: _get_localized_value(key, original_language)
        for key in known_keys_data
    }
    known_key_names = set(known_keys.keys())
    actual_key_names = set(actual_keys.keys())

    create_keys = {}
    for key_name in actual_key_names - known_key_names:
        create_keys[key_name] = actual_keys[key_name]

    update_keys = {}
    for key_name in actual_key_names & known_key_names:
        if actual_keys[key_name] != known_keys[key_name]:
            update_keys[key_name] = actual_keys[key_name]

    delete_keys = list(known_key_names - actual_key_names)

    if create_keys or update_keys or delete_keys:
        client.change_keys(
            keyset,
            create_keys=create_keys,
            update_keys=update_keys,
            delete_keys=delete_keys,
            original_language=original_language,
        )


def _get_worse_status(old_status, new_status):
    old_priority = PRIORITIES.get(old_status)
    new_priority = PRIORITIES.get(new_status)
    if not old_priority:
        return new_status
    if not new_priority:
        return old_status
    if new_priority < old_priority:
        return new_status
    return old_status


def get_keyset_status(client, keyset, languages=None, keys_data=None):
    keys_data = keys_data or client.get_keys(keyset)
    if not languages:
        languages = set()
        for key in keys_data:
            for lang in key['translations']:
                languages.add(lang)

    keyset_status = {}
    for key in keys_data:
        translations = key['translations']
        for _lang in languages:
            status = translations.get(_lang, {}).get('status', REQUIRES_TRANSLATION)
            lang = _lang.lower()
            keyset_status[lang] = _get_worse_status(keyset_status.get(lang), status)
    return keyset_status


def get_translations(client, keyset, status=None, keys_data=None):
    translations = defaultdict(lambda: defaultdict(dict))
    keys_data = keys_data or client.get_keys(keyset)
    for key in keys_data:
        m = _key_re.match(key['name'])
        if not m:
            continue
        app_label, model, pk, field = m.groups()
        for _lang, translation in key['translations'].items():
            if status is None or translation['status'] == status:
                lang = _lang.lower()
                translations[(app_label, model, pk)][field][lang] = translation['payload']['singular_form']
    return translations


def _get_datetime(s: str) -> datetime.datetime:
    return timezone.make_aware(dateutil.parser.isoparse(s), timezone.utc)


def run_translation_process(client, survey_id, languages=None):
    languages = languages or ['ru', 'en']
    keyset = f'surveyme.survey.{survey_id}'

    create_or_modify_keyset(client, keyset, languages)

    actual_keys = _get_actual_keys(survey_id=survey_id)
    create_or_modify_keys(client, keyset, actual_keys)

    keyset_status = get_keyset_status(client, keyset, languages=languages)

    ct = ContentType.objects.get_for_model(Survey)
    tk, _ = TankerKeyset.objects.get_or_create(name=keyset, content_type=ct, object_id=survey_id)
    tk.last_updated_at = None
    tk.keys = list(actual_keys.keys())
    tk.languages = keyset_status
    tk.save(update_fields=['last_updated_at', 'languages', 'keys'])


def get_tankerkeysets_for_update(client):
    ct = ContentType.objects.get_for_model(Survey)
    queryset = (
        TankerKeyset.objects.using(settings.DATABASE_ROLOCAL)
        .filter(content_type=ct)
    )
    actual_keysets = {
        tk.name: tk
        for tk in queryset
    }
    if not actual_keysets:
        return

    known_keysets = {
        keyset['name']: keyset
        for keyset in client.get_keysets()
    }
    if not known_keysets:
        return

    for keyset_name in set(actual_keysets.keys()) & set(known_keysets.keys()):
        actual_keyset = actual_keysets[keyset_name]
        actual_ts = actual_keyset.last_updated_at
        known_ts = _get_datetime(known_keysets[keyset_name]['last_commit_ts'])
        if not actual_ts or actual_ts < known_ts:
            yield actual_keyset


def update_translations(client, tk):
    keyset = tk.name
    languages = list(tk.languages.keys())
    keys_data = client.get_keys(keyset)

    translations = get_translations(client, keyset, keys_data=keys_data)
    for (app_label, model, pk), data in translations.items():
        try:
            ct = ContentType.objects.get_by_natural_key(app_label, model)
            model_class = ct.model_class()
            obj = model_class.objects.get(pk=pk)
            if hasattr(obj, 'translations'):
                obj.translations = data
                obj.save(update_fields=['translations'])
        except ObjectDoesNotExist:
            pass

    keyset_status = get_keyset_status(client, keyset, languages=languages, keys_data=keys_data)

    tk.last_updated_at = timezone.now()
    tk.languages = keyset_status
    tk.save(update_fields=['last_updated_at', 'languages'])


def update_status(client, tk):
    keyset = tk.name
    languages = list(tk.languages.keys())

    keyset_status = get_keyset_status(client, keyset, languages=languages)

    tk.languages = keyset_status
    tk.save(update_fields=['languages'])
    return tk


def get_translation_status(survey_id):
    client = TankerClient(settings.TANKER_PROJECT)
    ct = ContentType.objects.get_for_model(Survey)
    try:
        tk = TankerKeyset.objects.get(content_type=ct, object_id=survey_id)
    except TankerKeyset.DoesNotExist:
        logger.warning('Keyset for object %s was not found', survey_id)
    else:
        tk = update_status(client, tk)
        return {
            lang: str(STATUSES.get(status))
            for (lang, status) in tk.languages.items()
        }
