#!/usr/bin/env python
from __future__ import print_function

from getpass import getuser
from os import getcwd
import subprocess
from textwrap import dedent

import click

import nbformat
from nbconvert.preprocessors.clearoutput import ClearOutputPreprocessor
from pgcontents.constants import (
    ALEMBIC_DIR_LOCATION,
    DB_URL_ENVVAR,
)
from pgcontents.crypto import single_password_crypto_factory
from pgcontents.pgmanager import PostgresContentsManager

from pgcontents.utils.migrate import (
    temp_alembic_ini,
    upgrade,
)
from tornado.web import HTTPError


@click.group(context_settings=dict(help_option_names=['-h', '--help']))
def main():
    """
    Create or manage a pgcontents database.

    For documentation on sub-commands, do:

    pgcontents [COMMAND] --help
    """
    pass


# Options
_db_url = click.option(
    '-l', '--db-url',
    type=click.STRING,
    help='SQLAlchemy connection string for database.',
    envvar=DB_URL_ENVVAR,
    prompt="File Database URL",
    confirmation_prompt=True,
)
_directory = click.option(
    '-d', '--directory',
    type=click.Path(
        exists=True,
        writable=True,
        resolve_path=True,
    ),
    help="Path to a local directory.",
    default=getcwd(),
)
_users = click.option(
    '-u', '--users',
    type=click.STRING,
    help="Comma-separated list of users to sync.",
    default=getuser(),
)


CONFIRM_MIGRATION = dedent(
    """
    About to run schema migrations against supplied database URL.  If you have
    stored data from a previous pgcontents installation, it may not be
    correctly preserved.

    It is *HIGHLY* recommended that you back up stored data before proceeding.

    Proceed?"""
)


@main.command('init')
@_db_url
@click.option(
    '-r', '--revision',
    type=click.STRING,
    help="Revision ID to upgrade to.",
    default='head',
)
@click.option(
    '--prompt/--no-prompt',
    default=True,
    help="If passed, don't prompt for confirmation before migrating DB.",
)
def init(db_url, revision, prompt):
    """
    Migrate a database to the current pgcontents schema.
    """
    click.echo("Initializing pgcontents...")
    if prompt:
        click.confirm(CONFIRM_MIGRATION, abort=True)
    upgrade(db_url, revision)

    click.echo('Initialization completed successfully.')


@main.command()
@_db_url
def gen_migration(db_url):
    """
    Use alembic revision --autogenerate to create a new migration from
    schema.py.

    You probably don't need to use this unless you're doing local development
    or changing the default pgcontents schema.
    """
    with temp_alembic_ini(ALEMBIC_DIR_LOCATION, db_url) as alembic_ini:
        subprocess.check_call(
            ['alembic', '-c', alembic_ini, 'revision', '--autogenerate']
        )


@main.command()
@click.option(
    '-u', '--user',
    help='Owner of the notebook to be fetched.',
)
@click.option(
    '-f', '--filename',
    help='Name of the file to fetch in the DB.',
)
@click.option(
    '-o', '--output',
    help="Local filesystem destination.",
    type=click.Path(
        file_okay=True,
        dir_okay=False,
        writable=True,
    ),
)
@click.option(
    '-k', '--key',
    help="Decryption key.",
    type=click.STRING,
    envvar='PGCONTENTS_DECRYPTION_KEY',
)
@click.option(
    '-t', '--type',
    help="Type of file to fetch (notebook or file).",
    default='notebook',
    type=click.Choice(['file', 'notebook']),
    show_default=True,
)
@click.option(
    '--clear-output',
    help="Clear notebook output before writing?",
    default=False,
    is_flag=True,
)
@_db_url
def fetch(db_url, user, filename, key, output, type, clear_output):
    """Fetch a notebook from the database to the local filesystem.
    """
    if db_url is None:
        raise click.UsageError("-l/--db-url is required")
    if user is None:
        raise click.UsageError("-u/--user is required")
    if filename is None:
        raise click.UsageError("-f/--filename is required")
    if output is None:
        output = filename

    crypto = single_password_crypto_factory(key)(user)

    mgr = PostgresContentsManager(
        db_url=db_url,
        user_id=user,
        # User should already exist.
        create_directory_on_startup=False,
        create_user_on_startup=False,
        crypto=crypto,
    )

    try:
        result = mgr.get(filename, content=True, type=type)
    except HTTPError as e:
        if e.status_code == 404:
            raise click.ClickException("No such file: {!r}".format(filename))
        elif e.status_code == 500:
            raise click.ClickException(
                "Failed to load file: {!r}. Is the decryption key correct?"
                .format(filename)
            )
        else:
            raise click.ClickException("Unknown error: %s" % e)

    nb = nbformat.from_dict(result['content'])
    if clear_output:
        ClearOutputPreprocessor().preprocess(nb, resources=None)

    nbformat.write(nb, open(output, 'w'), version=nbformat.NO_CONVERT)


if __name__ == "__main__":
    main()
