import logging
from typing import List, Tuple
from uuid import uuid1

from django.core.serializers.json import Serializer as JsonSerializer
from django.db import models

from staff.departments.models import DepartmentChain
from staff.emission.django.emission_master.models import MasterLog, Sent, NotSent


logger = logging.getLogger(__name__)


class Serializer(JsonSerializer):
    def handle_field(self, obj, field):
        if field.model == DepartmentChain and field.name in ['chiefs', 'hr_partners']:
            self._current[field.name] = field.value_from_object(obj)
        else:
            return super().handle_field(obj, field)

    def handle_fk_field(self, obj, field):
        self._current[field.name] = field.value_from_object(obj)

    def handle_m2m_field(self, obj, field):
        pass


class MasterOrmStorage:
    id_field = 'id'
    model_class = MasterLog

    @property
    def values_args(self):
        return self.id_field, 'data', 'action', 'creation_time'

    def get_queryset(self):
        return self.model_class.objects.all()

    def get_unsent_queryset(self) -> models.QuerySet:
        return MasterLog.objects.exclude(notsent=None).order_by('id').values(*self.values_args)

    def mark_sent(self, qs: models.QuerySet):
        messages_ids = list(qs.values_list('id', flat=True))
        NotSent.objects.filter(entry_id__in=messages_ids).delete()

        to_create = []
        for master_log_item_id in messages_ids:
            to_create.append(Sent(entry_id=master_log_item_id))

        logger.info('Move %s messages to sent', len(to_create))
        Sent.objects.bulk_create(to_create)

    def append(self, data, action):
        """Adds a new message to the log"""
        entry = self.insert(msg_id=None, data=data, action=action)
        NotSent.objects.create(entry=entry)

    def serialize_objects(self, objs):
        """Serialize a data object to add the message"""
        return Serializer().serialize(objs)

    def delete_one(self, msg_id):
        """Removes the message from the log by message id"""
        self.get_queryset().get(**{self.id_field: msg_id}).delete()

    def get_slice(self, start, stop=None, max_rows=None):
        """Returns slice as the log iterator"""
        qs = (
            self.get_filtered_qs()
            .values(*self.values_args)
            .filter(**{self.id_field + '__gte': start})
            .order_by(self.id_field)
        )

        if stop is not None:
            qs = qs.filter(**{self.id_field + '__lte': stop})
        if max_rows:
            qs = qs[:max_rows]
        return qs.iterator()

    def get_next_id(self, current_id):
        """Returns next message id in the log after current_id"""
        return (
            self.get_filtered_qs()
            .filter(**{'{}__gt'.format(self.id_field): current_id})
            .values_list(self.id_field, flat=True)
            .order_by(self.id_field)
            .first()
        )

    def get_last_id(self):
        """Returns last message id in the log"""
        return self.get_filtered_qs().values_list(self.id_field, flat=True).order_by('-' + self.id_field).first()

    def get_one(self, msg_id):
        """Returns the message as dict object by message id"""
        return self.get_queryset().values(*self.values_args).get(**{self.id_field: msg_id})

    def bulk_create(self, objects_data: List[Tuple[str, str]]):
        batch_id = uuid1()
        self.model_class.objects.bulk_create([
            self.model_class(data=data, action=action, batch_id=batch_id) for data, action in objects_data
        ])

        ids = self.model_class.objects.filter(batch_id=batch_id).values_list('id', flat=True)
        NotSent.objects.bulk_create([NotSent(entry_id=single_id) for single_id in ids])

    def get_filtered_qs(self):
        return self.get_queryset()

    def insert(self, msg_id, data, action):
        """Adds an existing message with the message id in the log"""
        # TODO Надо учитват время, когда вставляется в середину.
        entry, created = self.get_queryset().get_or_create(
            defaults={'data': data, 'action': action},
            **{self.id_field: msg_id}
        )

        if not created:
            entry.data = data
            entry.save()

        return entry

    def cut(self, date):
        """Deletes old messages in the log"""
        self.get_queryset().filter(creation_time__lt=date).delete()
