import datetime
import getpass
import logging

from celery import shared_task
from django.core.exceptions import ObjectDoesNotExist
from django.db.transaction import atomic
from django.utils import timezone
from ylog.context import log_context

from intranet.search.core import storages, sources
from intranet.search.core.storages import RevisionStorage, IndexationStorage
from intranet.search.core.utils import lock, convert_timeout
from intranet.search.core.utils.config import get_default_service

log = logging.getLogger(__name__)


def parse_to_list(data, separator=','):
    """ Парсит строку с перечислением в список строк
    """
    if isinstance(data, (list, tuple)):
        return list(data)
    else:
        lines = data.split(separator) if data else []
        return [l.strip() for l in lines]


def get_indexation_revision(organization_id, search, index='', backend='platform',
                            service=None, **options):
    """  Получение ревизии для индексации.
    Если явно задан revision_id - то берем эту ревизию.
    Если явно задано new_revision - создаем новую ревизию.
    Иначе берем последнюю активную ревизию для поиска, если указано время жизни (revision_ttl)
    и ревизия старее его, то создаем новую ревизию, иначе - индексируем в неё.
    """
    rev_storage = RevisionStorage()

    if options.get('revision_id'):
        return rev_storage.get_by_id(options.get('revision_id'))
    else:
        try:
            revision = rev_storage.get_active(search=search, index=index, backend=backend,
                                              organization_id=organization_id, limit=1)[0]
        except storages.DoesNotExist:
            revision = None

    new_revision = options.get('new_revision')

    if revision and options.get('revision_ttl'):
        ttl = convert_timeout(options.get('revision_ttl'))
        if timezone.now() - revision['date'] > datetime.timedelta(seconds=ttl):
            new_revision = True

    if not revision or new_revision:
        if not service:
            service = revision['service'] if revision else get_default_service(search, index)

        status = 'new'
        if not revision:
            # если в индексе нет активной ревизии, то первую создаем сразу активной
            status = 'ready' if options.get('shadow_revision') else 'active'
        revision = rev_storage.create(search=search, index=index, backend=backend,
                                      service=service, organization_id=organization_id,
                                      status=status)
    return revision


def _get_lock(organization_id, search, backend='platform', index='', nolock=False, lock_timeout='4h'):
    if not nolock:
        lock_name = 'isearch-reindex:%s' % ':'.join(filter(None, (str(organization_id), search, backend, index)))
        # Пытаемся взять эксклюзивный лок
        indexation_lock = lock.manager.lock(lock_name,
                                            timeout=convert_timeout(lock_timeout),
                                            block=False)

        if not indexation_lock.acquire():
            log.info('Lock is already acquired. Exit')
            return None, False

        log.debug('Lock is acquired succesfully')
        lock_context = indexation_lock.get_context()
    else:
        lock_context = None
    return lock_context, True


def _reindex_one(organization_id, search, index='', nolock=False, lock_timeout='4h', debug=False,
                 service='', use_ts=False, ts=None, keys='', comment='', user=None, load_percent=50,
                 **options):
    if not user:
        user = getpass.getuser()

    backend = 'platform'
    log.debug('Reindex starting...')

    # загружем класс индексатора
    source_class = sources.load(search, index)

    lock_context, is_successful = _get_lock(organization_id, search, backend, index, nolock, lock_timeout)
    if not is_successful:
        return

    with atomic():
        revision = get_indexation_revision(organization_id, search, index, backend, service=service,
                                           **options)
        indexation_storage = IndexationStorage()
        indexation_id = indexation_storage.create(revision, comment, user)

        with log_context(indexation_id=indexation_id, revision=revision['id'],
                         push_id=options.get('push_id')):
            last_done_indexation = indexation_storage.get_latest_indexation(
                search=search,
                index=index,
                revision=revision['id'],
                status__in=['done']
            )

            if not ts and use_ts and last_done_indexation:
                start_time = last_done_indexation['start_time']
                ts = (start_time - datetime.timedelta(minutes=30)).strftime('%s')

            options.update({
                'index': index,
                'debug': debug,
                'revision': revision,
                'ts': ts,
                'indexation_id': indexation_id,
                'keys': parse_to_list(keys),
                'lock_context': lock_context,
                'load_percent': int(load_percent),
            })
            if 'document_storages' in options:
                options['document_storages'] = parse_to_list(options.get('document_storages'))

            source = source_class(options)
            log.debug('Source indexer loaded: %s' % source)
            indexation_storage.set_indexer(indexation_id, source)

    source.makeindex()

    return indexation_id


def _pause_one(indexation_id, **kwargs):
    indexation_storage = IndexationStorage()
    indexer = indexation_storage.get_indexer(id_=indexation_id)
    indexer.pause()


def _continue_one(indexation_id, **kwargs):
    indexation_storage = IndexationStorage()
    indexer = indexation_storage.get_indexer(id_=indexation_id)
    organization = indexer.revision['organization']
    indexer.release_lock()  # just in case the release didn't work on pausing
    with log_context(indexation_id=indexation_id, organization_id=organization.get('id')):
        lock_context, is_successful = _get_lock(
            organization_id=organization.get('id'), search=indexer.revision.get('search')
        )
        logging.warning(
            'Lock acquisition for _continue_one for the indexation %s was %s',
            indexation_id, 'ok' if is_successful else 'not ok'
        )
        if not is_successful:
            return
        indexer.continue_indexation(lock_context=lock_context)


@shared_task(name='isearch.tasks.reindex_one')
def reindex_one(organization_id, search, *args, **kwargs):
    with log_context(organization_id=organization_id, search=search):
        _reindex_one(organization_id, search, *args, **kwargs)


@shared_task(name='isearch.tasks.pause_one')
def pause_one(indexation_id, **kwargs):
    with log_context(indexation_id=indexation_id):
        _pause_one(indexation_id, **kwargs)


@shared_task(name='isearch.tasks.continue_one')
def continue_one(indexation_id, **kwargs):
    with log_context(indexation_id=indexation_id):
        _continue_one(indexation_id, **kwargs)


def get_reindexing_organizations(search, organizations_ids, index='', organizations_limit=None):
    organization_storage = storages.OrganizationStorage()

    if not organizations_ids:
        if organizations_limit:
            return organization_storage.get_by_indexation_time(search, index, organizations_limit)
        return organization_storage.get_by_service(search)

    if isinstance(organizations_ids, str):
        organizations_ids = organizations_ids.strip(', ').split(',')
        organizations_ids = [org.strip() for org in organizations_ids if org.strip()]

    organizations = []
    for organization_id in organizations_ids:
        try:
            organization = organization_storage.get_one_by_service(search, organization_id)
        except ObjectDoesNotExist:
            log.error('Cannot get organization %s for indexation %s', organization_id, search)
            continue
        else:
            organizations.append(organization)

    return organizations


def reindex(search, organizations=None, **kwargs):
    reindex_run = reindex_one if kwargs.get('noqueue', False) else reindex_one.delay
    reindexing_organizations = get_reindexing_organizations(
        search, organizations, index=kwargs.get('index', ''),
        organizations_limit=kwargs.get('organizations_limit'))
    log.debug('Trying to start indexation for organizations: %s' % reindexing_organizations)

    for organization in reindexing_organizations:
        reindex_run(organization['id'], search, **kwargs)


def pause_index(indexation_id, **kwargs):
    pause_run = pause_one if kwargs.get('noqueue', False) else pause_one.delay
    log.debug('Trying to pause indexation %s', indexation_id)
    pause_run(indexation_id, **kwargs)


def continue_index(indexation_id, **kwargs):
    continue_run = continue_one if kwargs.get('noqueue', False) else continue_one.delay
    log.debug('Trying to continue indexation %s', indexation_id)
    continue_run(indexation_id, **kwargs)
