"""Tornado handlers for nbexamples web service."""

import os
import shutil
import subprocess as sp
import json
import glob
import itertools
import errno
import pwd

from tornado import web

import nbformat
from notebook.utils import url_path_join as ujoin
from notebook.base.handlers import IPythonHandler
from traitlets import Unicode
from traitlets.config import LoggingConfigurable


class Examples(LoggingConfigurable):
    reviewed_example_dir = Unicode('', config=True, help='Directory of reviewed notebooks, relative to NotebookApp.notebook_dir')
    unreviewed_example_dir = Unicode('', config=True, help='Directory of unreviewed notebooks, relative to NotebookApp.notebook_dir')

    def _reviewed_example_dir_default(self):
        return self.parent.notebook_dir

    def _unreviewed_example_dir_default(self):
        return self.parent.notebook_dir

    def make_unique_filename(self, abs_fn, insert='-Clone'):
        path, filename = os.path.split(abs_fn)
        basename, ext = os.path.splitext(filename)
        for i in itertools.count():
            insert_i = '{}{}'.format(insert, i)
            name = '{basename}{suffix}{ext}'.format(basename=basename,
                                                    suffix=insert_i,
                                                    ext=ext)
            new_abs_fn = os.path.join(path, name)
            if not os.path.exists(new_abs_fn):
                break
        return new_abs_fn

    def list_examples(self):
        categories = ['reviewed', 'unreviewed']
        dirs = [self.reviewed_example_dir, self.unreviewed_example_dir]
        all_examples = []
        uid = os.getuid()
        for category, d in zip(categories, dirs):
            filepaths = glob.glob(os.path.join(d, '*.ipynb'))
            examples = [{'filepath': os.path.abspath(fp)} for fp in filepaths]
            for example in examples:
                node = nbformat.read(example['filepath'], nbformat.NO_CONVERT)
                st = os.stat(example['filepath'])
                try:
                    user = pwd.getpwuid(st.st_uid)
                except KeyError:
                    example['user'] = None
                else:
                    example['user'] = user.pw_gecos or user.pw_name
                example['datetime'] = st.st_mtime
                example['filename'] = os.path.basename(example['filepath'])
                example['metadata'] = node.metadata
                example['category'] = category
                example['basename'] = os.path.basename(example['filepath'])
                example['owned'] = st.st_uid == uid
            all_examples.extend(examples)
        return all_examples

    def fetch_example(self, example_id, dest):
        abs_dest = os.path.join(self.parent.notebook_dir, dest)
        if not abs_dest.endswith('.ipynb'):
            abs_dest += '.ipynb'
        # Give the target a unique suffix to avoid overwriting anything
        if os.path.exists(abs_dest):
            abs_dest = self.make_unique_filename(abs_dest)
        # Make a copy of the example notebook, stripping output.
        p = sp.Popen(['jupyter', 'nbconvert', example_id,
                      '--Exporter.preprocessors=["nbexamples.strip_output.StripOutput"]',
                      '--to', 'notebook', '--output', abs_dest],
                     stdout=sp.PIPE, stderr=sp.PIPE)
        output, err = p.communicate()
        retcode = p.poll()
        if retcode != 0:
            raise RuntimeError('jupyter nbconvert exited with error {}'.format(
                               err))
        # Return the possibly suffixed filename
        return os.path.split(abs_dest)[1]

    def submit_example(self, user_filepath):
        # Make a copy of the example notebook
        src = os.path.join(self.parent.notebook_dir, user_filepath)
        filename = os.path.basename(user_filepath)
        dest = os.path.join(self.unreviewed_example_dir, filename)
        try:
            shutil.copyfile(src, dest)
        except OSError as ex:
            # python 2/3 compatibility permission error check
            if ex.errno == errno.EACCES:
                if os.path.exists(dest):
                    raise web.HTTPError(401, 'Another user already shared a notebook with the name {}'.format(filename))
                else:
                    raise web.HTTPError(401, 'Could not write to the examples directory')
        return dest

    def preview_example(self, filepath):
        fp = filepath  # for brevity
        if not os.path.isfile(fp):
            raise web.HTTPError(404, "Example not found: %s" % fp)
        p = sp.Popen(['jupyter', 'nbconvert', '--to', 'html', '--stdout', fp],
                     stdout=sp.PIPE, stderr=sp.PIPE)
        output, _ = p.communicate()
        retcode = p.poll()
        if retcode != 0:
            raise RuntimeError('nbconvert exited with code {}'.format(retcode))
        return output.decode()

    def delete_example(self, filepath):
        os.remove(filepath)


class BaseExampleHandler(IPythonHandler):

    @property
    def manager(self):
        return self.settings['example_manager']


class ExamplesHandler(BaseExampleHandler):

    @web.authenticated
    def get(self):
        self.finish(json.dumps(self.manager.list_examples()))


class ExampleActionHandler(BaseExampleHandler):

    @web.authenticated
    def get(self, action):
        example_id = self.get_argument('example_id')
        if action == 'preview':
            self.finish(self.manager.preview_example(example_id))
        elif action == 'fetch':
            dest = self.get_argument('dest')
            dest = self.manager.fetch_example(example_id, dest)
            self.redirect(ujoin(self.base_url, 'notebooks', dest))
        elif action == 'submit':
            dest = self.manager.submit_example(example_id)
            self.redirect(ujoin(self.base_url, 'tree#examples' + dest))
        elif action == 'delete':
            self.manager.delete_example(example_id)
            self.redirect(ujoin(self.base_url))


# -----------------------------------------------------------------------------
# URL to handler mappings
# -----------------------------------------------------------------------------


_example_action_regex = r"(?P<action>fetch|preview|submit|delete)"

default_handlers = [
    (r"/examples", ExamplesHandler),
    (r"/examples/%s" % _example_action_regex, ExampleActionHandler),
]


def load_jupyter_server_extension(nbapp):
    """Load the nbserver"""
    webapp = nbapp.web_app
    webapp.settings['example_manager'] = Examples(parent=nbapp)
    base_url = webapp.settings['base_url']

    ExampleActionHandler.base_url = base_url  # used to redirect after fetch
    webapp.add_handlers(".*$", [
        (ujoin(base_url, pat), handler)
        for pat, handler in default_handlers
    ])
