from wq.core import wq
import click
import os
import json
from glob import glob


@wq.command()
@click.argument('version')
@click.option(
    '--template',
    type=click.Path(),
    help="Override service worker template"
)
@click.option(
    '--output',
    type=click.Path(),
    default="./htdocs/service-worker.js",
    help="Destination file (default: ./htdocs/service-worker.js)"
)
@click.option(
    '--cache',
    type=click.Path(),
    multiple=True,
    help="File(s) or directories to cache"
)
@click.option(
    '--scope',
    type=str,
    default="/",
    help="Path prefix for service worker and cached files"
)
@click.option(
    '--timeout',
    type=int,
    default=400,
    help="Timeout to use before falling back to cache."
)
def serviceworker(version, template, output, cache, scope, timeout):
    """
    Generate a service-worker.js.  The default service worker will function as
    follows:

    \b
    1. While installing, precache all of the listed paths.
    2. While running, attempt to fetch (and re-cache) the latest version
       of each file, but fall back to the cached version after the specified
       timeout.

    This ensures the app can work offline as needed, without requiring a double
    refresh whenever a new version is available.  Specify the --template option
    to override the default.

    The list of files to cache should be relative to the directory containing
    service-worker.js.  Wildcard paths will be resolved to existing filenames,
    so this command should generally be run *after* ./manage.py collectstatic.

    Note: If you are using wq with npm, a service worker will be generated by
    create-react-app, so you do not need to use this command.
    """

    if template:
        with open(template) as f:
            template = f.read()
    else:
        template = SW_TMPL

    if not scope.endswith('/'):
        scope += '/'

    basedir = os.path.dirname(output)
    paths = []

    for path in cache:
        if path.startswith('/'):
            path = path[1:]
        if '*' not in path:
            paths.append(scope + path)
            continue
        for filename in glob(os.path.join(basedir, path)):
            paths.append(filename.replace(basedir + '/', scope))

    cache = ",".join(json.dumps(path) for path in paths)

    with open(output, 'w') as f:
        f.write(
            template.replace("{{VERSION}}", version)
            .replace("{{CACHE}}", cache)
            .replace("{{TIMEOUT}}", str(timeout))
        )


SW_TMPL = """const CACHE_NAME = 'wq-cache-v1';

// App Version {{VERSION}}

const cacheUrls = [{{CACHE}}];

self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME).then(cache => cache.addAll(cacheUrls))
    );
});

self.addEventListener('fetch', event => {
    if (!cacheUrls.includes(new URL(event.request.url).pathname)) {
        return;
    }
    event.respondWith(
        new Promise((resolve, reject) => {
            const timeout = setTimeout(reject, {{TIMEOUT}});
            fetch(event.request).then(response => {
                clearTimeout(timeout);
                const cacheResponse = response.clone();
                caches
                    .open(CACHE_NAME)
                    .then(cache => cache.put(event.request, cacheResponse));
                resolve(response);
            }, reject);
        }).catch(() => {
            return caches
                .open(CACHE_NAME)
                .then(cache => cache.match(event.request))
                .then(response => response || Promise.reject('no-match'));
        })
    );
});
"""
