from itertools import chain
from django.db import (
    connection,
    migrations,
    OperationalError,
    ProgrammingError,
)


def squash_data(squashed):
    '''Returns a tuple of the squashed_keys and the key position to begin
    processing replace and operation lists'''

    cm = current_migration()
    squashed_keys = sorted(squashed.keys())
    if cm is None:
        return squashed_keys, 0

    try:
        key_index = squashed_keys.index(cm.name) + 1
    except ValueError:
        key_index = 0
    return squashed_keys, key_index


def current_migration(exclude_squashed=True):
    '''Get the latest migration non-squashed migration'''
    try:
        recorder = migrations.recorder.MigrationRecorder(connection)
        migration_qs = recorder.migration_qs.filter(app='main')
        if exclude_squashed:
            migration_qs = migration_qs.exclude(name__contains='squashed')
        return migration_qs.latest('id')
    except (recorder.Migration.DoesNotExist, OperationalError, ProgrammingError):
        return None


def replaces(squashed, applied=False):
    '''Build a list of replacement migrations based on the most recent non-squashed migration
    and the provided list of SQUASHED migrations. If the most recent non-squashed migration
    is not present anywhere in the SQUASHED dictionary, assume they have all been applied.

    If applied is True, this will return a list of all the migrations that have already
    been applied.
    '''
    squashed_keys, key_index = squash_data(squashed)
    if applied:
        return [(b'main', key) for key in squashed_keys[:key_index]]
    return [(b'main', key) for key in squashed_keys[key_index:]]


def operations(squashed, applied=False):
    '''Build a list of migration operations based on the most recent non-squashed migration
    and the provided list of squashed migrations. If the most recent non-squashed migration
    is not present anywhere in the `squashed` dictionary, assume they have all been applied.

    If applied is True, this will return a list of all the operations that have
    already been applied.
    '''
    squashed_keys, key_index = squash_data(squashed)
    op_keys = squashed_keys[:key_index] if applied else squashed_keys[key_index:]
    ops = [squashed[op_key] for op_key in op_keys]
    return [op for op in chain.from_iterable(ops)]
