#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for the sharing app.
"""

import argparse
import json
import os
import pathlib
import re

import augeas

from plinth import action_utils

APACHE_CONFIGURATION = '/etc/apache2/conf-available/sharing-freedombox.conf'


def parse_arguments():
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')

    subparsers.add_parser('list', help='List all existing shares')

    add_parser = subparsers.add_parser('add', help='Add a new share')
    add_parser.add_argument('--name', required=True, help='Name of the share')
    add_parser.add_argument('--path', required=True, help='Disk path to share')
    add_parser.add_argument('--groups', nargs='*',
                            help='List of groups that can access the share')
    add_parser.add_argument('--is-public', required=False, default=False,
                            action="store_true",
                            help='Allow public access to this share')

    remove_parser = subparsers.add_parser('remove',
                                          help='Remove an existing share')
    remove_parser.add_argument('--name', required=True,
                               help='Name of the share to remove')

    subparsers.required = True
    return parser.parse_args()


def load_augeas():
    """Initialize augeas for this app's configuration file."""
    aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
                        augeas.Augeas.NO_MODL_AUTOLOAD)
    aug.set('/augeas/load/Httpd/lens', 'Httpd.lns')
    aug.set('/augeas/load/Httpd/incl[last() + 1]', APACHE_CONFIGURATION)
    aug.load()

    aug.defvar('conf', '/files' + APACHE_CONFIGURATION)

    return aug


def subcommand_add(arguments):
    """Add a share to Apache configuration."""
    name = arguments.name
    path = arguments.path
    groups = arguments.groups
    is_public = arguments.is_public
    url = '/share/' + name

    if not os.path.exists(APACHE_CONFIGURATION):
        pathlib.Path(APACHE_CONFIGURATION).touch()

    aug = load_augeas()
    shares = _list(aug)
    if any([share for share in shares if share['name'] == name]):
        raise Exception('Share already present')

    aug.set('$conf/directive[last() + 1]', 'Alias')
    aug.set('$conf/directive[last()]/arg[1]', url)
    aug.set('$conf/directive[last()]/arg[2]', path)

    aug.set('$conf/Location[last() + 1]/arg', url)

    aug.set('$conf/Location[last()]/directive[last() + 1]', 'Include')
    aug.set('$conf/Location[last()]/directive[last()]/arg',
            'includes/freedombox-sharing.conf')

    if not is_public:
        aug.set('$conf/Location[last()]/directive[last() + 1]', 'Include')
        aug.set('$conf/Location[last()]/directive[last()]/arg',
                'includes/freedombox-single-sign-on.conf')

        aug.set('$conf/Location[last()]/IfModule/arg', 'mod_auth_pubtkt.c')
        aug.set('$conf/Location[last()]/IfModule/directive[1]', 'TKTAuthToken')
        for group_name in groups:
            aug.set(
                '$conf/Location[last()]/IfModule/directive[1]/arg[last() + 1]',
                group_name)
    else:
        aug.set('$conf/Location[last()]/directive[last() + 1]', 'Require')
        aug.set('$conf/Location[last()]/directive[last()]/arg[1]', 'all')
        aug.set('$conf/Location[last()]/directive[last()]/arg[2]', 'granted')

    aug.save()

    with action_utils.WebserverChange() as webserver_change:
        webserver_change.enable('sharing-freedombox')


def subcommand_remove(arguments):
    """Remove a share from Apache configuration."""
    url_to_remove = '/share/' + arguments.name

    aug = load_augeas()

    for directive in aug.match('$conf/directive'):
        if aug.get(directive) != 'Alias':
            continue

        url = aug.get(directive + '/arg[1]')
        if url == url_to_remove:
            aug.remove(directive)

    for location in aug.match('$conf/Location'):
        url = aug.get(location + '/arg')
        if url == url_to_remove:
            aug.remove(location)

    aug.save()

    with action_utils.WebserverChange() as webserver_change:
        webserver_change.enable('sharing-freedombox')


def _get_name_from_url(url):
    """Return the name of the share given the URL for it."""
    matches = re.match(r'/share/([a-z0-9\-]*)', url)
    if not matches:
        raise ValueError

    return matches[1]


def _list(aug=None):
    """List all Apache configuration shares."""
    if not aug:
        aug = load_augeas()

    shares = []

    for match in aug.match('$conf/directive'):
        if aug.get(match) != 'Alias':
            continue

        url = aug.get(match + '/arg[1]')
        path = aug.get(match + '/arg[2]')

        try:
            name = _get_name_from_url(url)
            shares.append({
                'name': name,
                'path': path,
                'url': '/share/' + name
            })
        except ValueError:
            continue

    for location in aug.match('$conf/Location'):
        url = aug.get(location + '/arg')

        try:
            name = _get_name_from_url(url)
        except ValueError:
            continue

        groups = []
        for group in aug.match(location + '//directive["TKTAuthToken"]/arg'):
            groups.append(aug.get(group))

        def _is_public():
            """Must contain the line 'Require all granted'."""
            require = location + '//directive["Require"]'
            return bool(aug.match(require)) and aug.get(
                require +
                '/arg[1]') == 'all' and aug.get(require +
                                                '/arg[2]') == 'granted'

        for share in shares:
            if share['name'] == name:
                share['groups'] = groups
                share['is_public'] = _is_public()

    return shares


def subcommand_list(_):
    """List all Apache configuration shares and print as JSON."""
    print(json.dumps({'shares': _list()}))


def main():
    """Parse arguments and perform all duties"""
    arguments = parse_arguments()

    subcommand = arguments.subcommand.replace('-', '_')
    subcommand_method = globals()['subcommand_' + subcommand]
    subcommand_method(arguments)


if __name__ == '__main__':
    main()
