#!/usr/bin/python3
# -*- mode: python -*-
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Configuration helper for Apache web server.
"""

import argparse
import glob
import os
import re
import subprocess

from plinth import action_utils


def parse_arguments():
    """Return parsed command line arguments as dictionary"""
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
    subparser = subparsers.add_parser('setup', help='Setup for Apache')
    subparser.add_argument(
        '--old-version', type=int, required=True,
        help='Earlier version of the app that is already setup.')
    subparser = subparsers.add_parser(
        'enable', help='Enable a site/config/module in apache')
    subparser.add_argument('--name',
                           help='Name of the site/config/module to enable')
    subparser.add_argument('--kind', choices=['site', 'config', 'module'])
    subparser = subparsers.add_parser(
        'disable', help='Disable a site/config/module in apache')
    subparser.add_argument('--name',
                           help='Name of the site/config/module to disable')
    subparser.add_argument('--kind', choices=['site', 'config', 'module'])
    subparser = subparsers.add_parser(
        'uwsgi-enable', help='Enable a site/config/module in UWSGI')
    subparser.add_argument('--name',
                           help='Name of the site/config/module to enable')
    subparser = subparsers.add_parser(
        'uwsgi-disable', help='Disable a site/config/module in UWSGI')
    subparser.add_argument('--name',
                           help='Name of the site/config/module to disable')

    subparsers.required = True
    return parser.parse_args()


def _get_sort_key_of_version(version):
    """Return the sort key for a given version string.

    Simple implementation hoping that PHP Apache module version numbers will be
    simple.

    """
    parts = []
    for part in version.split('.'):
        try:
            parts.append(int(part))
        except ValueError:
            parts.append(part)

    return parts


def _sort_versions(versions):
    """Return a list of sorted version strings."""
    return sorted(versions, key=_get_sort_key_of_version, reverse=True)


def _disable_mod_php(webserver):
    """Disable all mod_php versions.

    Idempotent and harmless if all or no PHP modules are identified.
    Problematic if only some modules are found.

    """
    paths = glob.glob('/etc/apache2/mods-available/php*.conf')
    versions = []
    for path in paths:
        match = re.search(r'\/php(.*)\.conf$', path)
        if match:
            versions.append(match[1])

    versions = _sort_versions(versions)

    for version in versions:
        webserver.disable('php' + version, kind='module')


def subcommand_setup(arguments):
    """Setup Apache configuration."""
    # Regenerate the snakeoil self-signed SSL certificate. This is so that
    # FreedomBox images don't all have the same certificate. When FreedomBox
    # package is installed via apt, don't regenerate. When upgrading to newer
    # version of Apache FreedomBox app and setting up for the first time don't
    # regenerate.
    if action_utils.is_disk_image() and arguments.old_version == 0:
        subprocess.run([
            'make-ssl-cert', 'generate-default-snakeoil', '--force-overwrite'
        ], check=True)
    # In case the certificate has been removed after ssl-cert is installed
    # on a fresh Debian machine.
    elif not os.path.exists('/etc/ssl/certs/ssl-cert-snakeoil.pem'):
        subprocess.run(['make-ssl-cert', 'generate-default-snakeoil'],
                       check=True)

    with action_utils.WebserverChange() as webserver:
        # Disable mod_php as we have switched to mod_fcgi + php-fpm. Disable
        # before switching away from mpm_prefork otherwise switching fails due
        # dependency.
        _disable_mod_php(webserver)

        # set the prefork worker model
        webserver.disable('mpm_worker', kind='module')
        webserver.disable('mpm_prefork', kind='module')
        webserver.enable('mpm_event', kind='module')

        # enable miscellaneous modules.
        webserver.enable('proxy', kind='module')
        webserver.enable('proxy_http', kind='module')
        webserver.enable('proxy_fcgi', kind='module')
        webserver.enable('proxy_html', kind='module')
        webserver.enable('rewrite', kind='module')
        webserver.enable('macro', kind='module')

        # Disable /server-status page to avoid leaking private info.
        webserver.disable('status', kind='module')

        # Enable HTTP/2 protocol
        webserver.enable('http2', kind='module')

        # Enable shared object cache needed for OSCP stapling. Needed by
        # mod_ssl.
        webserver.enable('socache_shmcb', kind='module')

        # switch to mod_ssl from mod_gnutls
        webserver.disable('gnutls', kind='module')
        webserver.enable('ssl', kind='module')

        # enable mod_alias for RedirectMatch
        webserver.enable('alias', kind='module')

        # enable mod_headers for HSTS
        webserver.enable('headers', kind='module')

        # Various modules for authentication/authorization
        webserver.enable('authnz_ldap', kind='module')
        webserver.enable('auth_pubtkt', kind='module')

        # enable some critical modules to avoid restart while installing
        # FreedomBox applications.
        webserver.disable('cgi', kind='module')  # For process MPMs
        webserver.enable('cgid', kind='module')  # For threaded MPMs
        webserver.enable('proxy_uwsgi', kind='module')
        webserver.enable('proxy_wstunnel', kind='module')

        # enable configuration for PHP-FPM
        webserver.enable('php-fpm-freedombox', kind='config')

        # enable users to share files uploaded to ~/public_html
        webserver.enable('userdir', kind='module')

        # setup freedombox site
        webserver.enable('freedombox', kind='config')

        # enable serving Debian javascript libraries
        webserver.enable('javascript-common', kind='config')

        # default sites
        webserver.enable('000-default', kind='site')
        webserver.disable('default-tls', kind='site')
        webserver.enable('default-ssl', kind='site')
        webserver.enable('plinth', kind='site')
        webserver.enable('plinth-ssl', kind='site')


# TODO: Check that the (name, kind) is a managed by FreedomBox before
# performing operation.
def subcommand_enable(arguments):
    """Enable an Apache site/config/module."""
    action_utils.webserver_enable(arguments.name, arguments.kind)


def subcommand_disable(arguments):
    """Disable an Apache site/config/module."""
    action_utils.webserver_disable(arguments.name, arguments.kind)


def subcommand_uwsgi_enable(arguments):
    """Enable uWSGI configuration and reload."""
    action_utils.uwsgi_enable(arguments.name)


def subcommand_uwsgi_disable(arguments):
    """Disable uWSGI configuration and reload."""
    action_utils.uwsgi_disable(arguments.name)


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()
