import logging
from datetime import datetime

from django.db.transaction import atomic
from ylog.context import log_context

from intranet.search.core.utils.config import get_default_service
from intranet.search.core import sources
from intranet.search.core.sources.utils import get_resource_data
from intranet.search.core.storages import PushStorage, RevisionStorage, IndexationStorage
from intranet.search.core.swarm import Indexer
from intranet.search.core.swarm.tasks import reindex_one

log = logging.getLogger(__name__)


def kill_duplicate_pushes(push_id, search, index, organization_id, keys=None):
    """ Убивает уже запущенные дублирующие пуши, чтобы не индексировать одновременно одно и то же
    """
    storage = IndexationStorage()
    running = storage.get_running(
        search=search,
        index=index,
        revision__organization=organization_id
    )
    for indexation in running:
        if push_id == indexation.get('options', {}).get('push_id'):
            continue

        # завершаем индексацию если новая индексация - полная (вообще без ключей),
        # либо если она такая же (ключи новой равны ключам запущенной)
        if not keys or sorted(indexation['options'].get('keys', [])) == sorted(keys):
            try:
                if indexation['options']['push_id']:
                    kill_push(
                        indexation['options']['push_id'],
                        cancel=True,
                        comment='Got the same newer push %s' % push_id
                    )
                indexer = storage.get_indexer(indexation=indexation)
                indexer.finish('fail', 'Got the same newer indexation')
            except Exception:
                log.exception('Cannot kill indexation %s', indexation['id'])


def handle_indexation_push(push_id, search, index='', organization_id=None, comment=None, **kwargs):
    """ Обрабатывает пуш объекта, создающий индексацию
    """
    with log_context(push_id=push_id, search=search, index=index, organization_id=organization_id):
        if not comment:
            comment = 'push %s' % push_id
            if kwargs.get('keys'):
                comment += ', keys: %s' % kwargs.get('keys')

        options = {
            'nolock': True,
            'user': 'indexer',
            'push_id': push_id,
            'comment': comment,
            'nort': False,
        }
        options.update(kwargs)
        log.info('Schedule `%s`.`%s` reindex from pushes %s, options: %s',
                 search, index or 'default', push_id, options)
        reindex_one.delay(organization_id, search, index, **options)
        kill_duplicate_pushes(push_id, search, index, organization_id, keys=kwargs.get('keys'))


def handle_single_push(push_id, data, search, index='', action='create',
                       organization_id=None, revisions=None, **kwargs):
    """ Обрабатывает пуш одного объекта в определенный индекс

    :param push_id: id пуша в базе
    :param data: данные для пуша. формат определяется конкретным индексатором
    :param search: название поиска
    :param index: название индекса
    :param action: действие, create или delete
    :param kwargs: дополнительные опции индексатора
    """
    with log_context(push_id=push_id, search=search, index=index, organization_id=organization_id):
        default_options = {
            'priority': Indexer.PRIORITY_HIGH,
            'nort': False,
            'push_id': push_id,
        }
        default_options.update(kwargs)
        source_class = sources.load(search, index)
        push_storage = PushStorage()

        if not revisions:
            rev_storage = RevisionStorage()
            revisions = rev_storage.get_actual(search=search, index=index, backend='platform',
                                               organization_id=organization_id)
        if not revisions:
            service = get_default_service(search=search, index=index)
            revision = rev_storage.get_or_create(search=search, index=index, backend='platform',
                                                 service=service, organization_id=organization_id, status='active')
            revisions = [revision]

        for revision in revisions:
            options = default_options.copy()
            options.update({
                'revision': revision,
                'index': revision['index'],
            })
            source = source_class(options)
            index_method = source.delete_resource if action == 'delete' else source.index_resource
            push_storage.set_instance_status(push_id, push_storage.STATUS_NEW, revision_id=revision['id'])
            index_method(data)


def log_push(resource, search, index='', push_type='unknown', comment=None,
             object_id=None, organization_id=None):
    data = get_resource_data(resource) if resource else {}
    return PushStorage().create(search, index, push_type, data, comment, object_id, organization_id)


def kill_push(push_id, cancel=False, comment=None):
    if not push_id:
        log.warning('Trying to kill push without push_id: %s. Skip it', push_id)
        return
    PushStorage().kill(push_id, cancel=cancel, comment=comment)


@atomic
def restart_push(push_id, only_failed=True, comment=None, **kwargs):
    with log_context(push_id=push_id):
        storage = PushStorage()
        push = storage.get(push_id)
        log.info('Trying to restart push: %s, retries: %s', push_id, push['retries'])

        comment = comment or f'restart push, only_failed={only_failed}'
        comment = f'[{datetime.now().isoformat()}] {comment}'
        storage.update(push_id, status=PushStorage.STATUS_RETRIED, retries=push['retries'] + 1,
                       start_time=datetime.now(), end_time=None,
                       comment='\n'.join([push['comment'], comment]))

        organization_id = push['organization']['id'] if push['organization'] else None
        retry_params = {'push_id': push_id, 'organization_id': organization_id, 'action': push['type']}
        retry_params.update(kwargs)

        for instance in storage.get_instances_for_retry(push_id, only_failed=only_failed):
            if instance['indexation']:
                log.info('Trying to restart push instance: %s, indexation: %s',
                         instance['id'], instance['indexation']['id'])
                handle_indexation_push(keys=instance['indexation']['options']['keys'],
                                       revision_id=instance['indexation']['revision'],
                                       search=instance['indexation']['search'],
                                       index=instance['indexation']['index'],
                                       **retry_params)
                storage.set_instance_status(push_id, status=storage.STATUS_RETRIED,
                                            indexation_id=instance['indexation']['id'])
            elif instance['revision']:
                log.info('Trying to restart push instance: %s, revision: %s',
                         instance['id'], instance['revision']['id'])
                handle_single_push(data=push['meta'], revisions=[instance['revision']],
                                   search=instance['revision']['search'],
                                   index=instance['revision']['index'],
                                   **retry_params)
