"""The ``celery shell`` program, used to start a REPL."""

import os
import sys
from importlib import import_module

import click

from celery.bin.base import CeleryCommand, CeleryOption, handle_preload_options


def _invoke_fallback_shell(locals):
    import code
    try:
        import readline
    except ImportError:
        pass
    else:
        import rlcompleter
        readline.set_completer(
            rlcompleter.Completer(locals).complete)
        readline.parse_and_bind('tab:complete')
    code.interact(local=locals)


def _invoke_bpython_shell(locals):
    import bpython
    bpython.embed(locals)


def _invoke_ipython_shell(locals):
    for ip in (_ipython, _ipython_pre_10,
               _ipython_terminal, _ipython_010,
               _no_ipython):
        try:
            return ip(locals)
        except ImportError:
            pass


def _ipython(locals):
    from IPython import start_ipython
    start_ipython(argv=[], user_ns=locals)


def _ipython_pre_10(locals):  # pragma: no cover
    from IPython.frontend.terminal.ipapp import TerminalIPythonApp
    app = TerminalIPythonApp.instance()
    app.initialize(argv=[])
    app.shell.user_ns.update(locals)
    app.start()


def _ipython_terminal(locals):  # pragma: no cover
    from IPython.terminal import embed
    embed.TerminalInteractiveShell(user_ns=locals).mainloop()


def _ipython_010(locals):  # pragma: no cover
    from IPython.Shell import IPShell
    IPShell(argv=[], user_ns=locals).mainloop()


def _no_ipython(self):  # pragma: no cover
    raise ImportError('no suitable ipython found')


def _invoke_default_shell(locals):
    try:
        import IPython  # noqa
    except ImportError:
        try:
            import bpython  # noqa
        except ImportError:
            _invoke_fallback_shell(locals)
        else:
            _invoke_bpython_shell(locals)
    else:
        _invoke_ipython_shell(locals)


@click.command(cls=CeleryCommand)
@click.option('-I',
              '--ipython',
              is_flag=True,
              cls=CeleryOption,
              help_group="Shell Options",
              help="Force IPython.")
@click.option('-B',
              '--bpython',
              is_flag=True,
              cls=CeleryOption,
              help_group="Shell Options",
              help="Force bpython.")
@click.option('--python',
              is_flag=True,
              cls=CeleryOption,
              help_group="Shell Options",
              help="Force default Python shell.")
@click.option('-T',
              '--without-tasks',
              is_flag=True,
              cls=CeleryOption,
              help_group="Shell Options",
              help="Don't add tasks to locals.")
@click.option('--eventlet',
              is_flag=True,
              cls=CeleryOption,
              help_group="Shell Options",
              help="Use eventlet.")
@click.option('--gevent',
              is_flag=True,
              cls=CeleryOption,
              help_group="Shell Options",
              help="Use gevent.")
@click.pass_context
@handle_preload_options
def shell(ctx, ipython=False, bpython=False,
          python=False, without_tasks=False, eventlet=False,
          gevent=False):
    """Start shell session with convenient access to celery symbols.

    The following symbols will be added to the main globals:
    - ``celery``:  the current application.
    - ``chord``, ``group``, ``chain``, ``chunks``,
      ``xmap``, ``xstarmap`` ``subtask``, ``Task``
    - all registered tasks.
    """
    sys.path.insert(0, os.getcwd())
    if eventlet:
        import_module('celery.concurrency.eventlet')
    if gevent:
        import_module('celery.concurrency.gevent')
    import celery
    app = ctx.obj.app
    app.loader.import_default_modules()

    # pylint: disable=attribute-defined-outside-init
    locals = {
        'app': app,
        'celery': app,
        'Task': celery.Task,
        'chord': celery.chord,
        'group': celery.group,
        'chain': celery.chain,
        'chunks': celery.chunks,
        'xmap': celery.xmap,
        'xstarmap': celery.xstarmap,
        'subtask': celery.subtask,
        'signature': celery.signature,
    }

    if not without_tasks:
        locals.update({
            task.__name__: task for task in app.tasks.values()
            if not task.name.startswith('celery.')
        })

    if python:
        _invoke_fallback_shell(locals)
    elif bpython:
        try:
            _invoke_bpython_shell(locals)
        except ImportError:
            ctx.obj.echo(f'{ctx.obj.ERROR}: bpython is not installed')
    elif ipython:
        try:
            _invoke_ipython_shell(locals)
        except ImportError as e:
            ctx.obj.echo(f'{ctx.obj.ERROR}: {e}')
    _invoke_default_shell(locals)
