import datetime
import logging
from typing import Optional

import pytz
from django.conf import settings
from django.db import models, transaction, connection
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from yql.api.v1.client import YqlClient

from idm.utils import chunkify

log = logging.getLogger(__name__)


class MetrikaCounter(models.Model):
    counter_id: int = models.CharField(_('Номер счетчика'), max_length=64, db_index=True)
    name: str = models.CharField(_('Имя счетчика'), max_length=1024, null=True, db_index=True)
    update_time: datetime.datetime = models.DateTimeField()

    INSERT_CHUNK_SIZE = 100
    UPDATE_TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
    RECENT_CHANGES_QUERY = '''
    USE hahn;

    $parse_update_time = DateTime::Parse("{update_time_format}");

    SELECT counter_id, name, update_time
    FROM `home/metrika/export/counters`
    WHERE
        counter_id IS NOT NULL AND
        DateTime::MakeDatetime($parse_update_time(update_time)) > (
            CurrentUtcDatetime() - DateTime::IntervalFromSeconds({seconds})
    );
    '''
    FULL_TABLE_QUERY = '''
    USE hahn;

    SELECT counter_id, name, update_time
    FROM `home/metrika/export/counters`
    WHERE
        counter_id IS NOT NULL;
    '''

    class Meta:
        verbose_name = _('Счетчик Метрики')
        verbose_name_plural = _('Счетчики Метрики')

    @classmethod
    @transaction.atomic
    def sync_from_yt(cls, full: bool = False, time_offset: int = 24 * 60 * 60):
        if full:
            query = cls.FULL_TABLE_QUERY.format(update_time_format=cls.UPDATE_TIME_FORMAT)
            log.info('Start sync whole YT table of Metrika counters')
            with connection.cursor() as cursor:
                cursor.execute(f'TRUNCATE TABLE {MetrikaCounter._meta.db_table} ')
        else:
            assert isinstance(time_offset, int) and time_offset > 0
            query = cls.RECENT_CHANGES_QUERY.format(update_time_format=cls.UPDATE_TIME_FORMAT, seconds=time_offset)
            log.info(f'Start sync changes of table of Metrika counters '
                     f'since {(timezone.now() - datetime.timedelta(seconds=time_offset)).isoformat()}')

        yql_client = YqlClient(token=settings.IDM_ROBOT_YQL_OAUTH_TOKEN)
        request = yql_client.query(query, syntax_version=1)
        request.run()
        counter_table, *_ = request.get_results()
        for counter_chunk in chunkify(counter_table.get_iterator(), chunk_size=cls.INSERT_CHUNK_SIZE):
            counters = {}
            for counter_id, name, update_time in counter_chunk:
                try:
                    update_time = datetime.datetime.strptime(update_time, cls.UPDATE_TIME_FORMAT) \
                        .replace(tzinfo=pytz.UTC)
                except ValueError:
                    log.info(f'Field update_time has invalid format {update_time}')
                    continue

                if counter_id is None:
                    continue
                counter_id = str(counter_id)

                if isinstance(name, bytes):
                    try:
                        name = name.decode()
                    except UnicodeDecodeError:
                        name = None
                if isinstance(name, str) and not name.isprintable():
                    log.warning(f'Invalid characters in name {name} of counter (id: {counter_id}). Skip.')
                    continue
                elif name is not None:
                    name = str(name)

                counters[counter_id] = cls(counter_id=counter_id, name=name, update_time=update_time)

            if not full:
                irrelevant_counters = set()
                for counter_id, counter_name in MetrikaCounter.objects.filter(
                        counter_id__in=set(counters)).values_list('counter_id', 'name'):
                    if counter_name != counters[counter_id].name:
                        irrelevant_counters.add(counter_id)  # изменившиеся удаляем
                    else:
                        del counters[counter_id]  # неизменные не перезаписываем
                cls.objects.filter(counter_id__in=irrelevant_counters).all().delete()

            cls.objects.bulk_create(counters.values())
            log.debug(f'Bulk of {len(counters)} counters write successfully')
        log.info('Sync Metrika counters finished correctly')

    @classmethod
    def get_counter_name(cls, counter_id: int) -> Optional[str]:
        if counter := MetrikaCounter.objects.filter(counter_id=counter_id).first():
            return counter.name
