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

import argparse
import configparser
import json
import os
import re
import subprocess

from plinth import action_utils

APACHE_CONF = '/etc/apache2/conf-available/zoph.conf'
DB_BACKUP_FILE = '/var/lib/plinth/backups-data/zoph-database.sql'


def parse_arguments():
    """Return parsed command line arguments as dictionary."""
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')

    subparsers.add_parser('pre-install',
                          help='Perform Zoph pre-install configuration')
    subparser = subparsers.add_parser('setup',
                                      help='Perform Zoph configuration setup')
    subparsers.add_parser('get-configuration',
                          help='Return the current configuration')
    subparser = subparsers.add_parser('set-configuration',
                                      help='Configure zoph')
    subparser.add_argument('--admin-user', help='Name of the admin user')
    subparser.add_argument('--enable-osm', help='Enable OpenSteetMap maps')
    subparsers.add_parser('is-configured', help='return true if configured')
    subparsers.add_parser('dump-database', help='Dump database to file')
    subparsers.add_parser('restore-database',
                          help='Restore database from file')

    subparsers.required = True
    return parser.parse_args()


def subcommand_pre_install(_):
    """Preseed debconf values before packages are installed."""
    action_utils.debconf_set_selections([
        'zoph zoph/dbconfig-install boolean true',
        'zoph zoph/mysql/admin-user string root'
    ])


def subcommand_get_configuration(_):
    """Return the current configuration."""
    configuration = {}
    process = subprocess.run(['zoph', '--dump-config'], stdout=subprocess.PIPE,
                             check=True)
    for line in process.stdout.decode().splitlines():
        name, value = line.partition(':')[::2]
        configuration[name.strip()] = value[1:]

    print(json.dumps(configuration))


def _zoph_configure(key, value):
    """Set a configure value in Zoph."""
    subprocess.run(['zoph', '--config', key, value], check=True)


def subcommand_setup(_):
    """Setup Zoph configuration."""
    _zoph_configure('import.enable', 'true')
    _zoph_configure('import.upload', 'true')
    _zoph_configure('import.rotate', 'true')
    _zoph_configure('path.unzip', 'unzip')
    _zoph_configure('path.untar', 'tar xvf')
    _zoph_configure('path.ungz', 'gunzip')

    # Maps using OpenStreetMap is enabled by default.
    _zoph_configure('maps.provider', 'osm')


def _get_db_name():
    """Return the name of the database configured by dbconfig."""
    config = configparser.ConfigParser()
    config.read_file(open('/etc/zoph.ini', 'r'))
    return config['zoph']['db_name'].strip('"')


def subcommand_set_configuration(arguments):
    """Setup Zoph Apache configuration."""
    _zoph_configure('interface.user.remote', 'true')

    # Note that using OpenSteetmap as a mapping provider is a very nice
    # feature, but some people may regard its use as a privacy issue
    if arguments.enable_osm:
        value = 'osm' if arguments.enable_osm == 'True' else ''
        _zoph_configure('maps.provider', value)

    if arguments.admin_user:
        # Edit the database to rename the admin user to FreedomBox admin user.
        admin_user = arguments.admin_user
        if not re.match(r'^[\w.@][\w.@-]+\Z', admin_user, flags=re.ASCII):
            # Check to avoid SQL injection
            raise ValueError('Invalid username')

        query = f"UPDATE zoph_users SET user_name='{admin_user}' \
                 WHERE user_name='admin';"

        subprocess.run(['mysql', _get_db_name()], input=query.encode(),
                       check=True)


def subcommand_is_configured(_):
    """Print whether zoph app is configured."""
    subprocess.run(['zoph', '--get-config', 'interface.user.remote'],
                   check=True)


def subcommand_dump_database(_):
    """Dump database to file."""
    db_name = _get_db_name()
    os.makedirs(os.path.dirname(DB_BACKUP_FILE), exist_ok=True)
    with open(DB_BACKUP_FILE, 'w') as db_backup_file:
        subprocess.run(['mysqldump', db_name], stdout=db_backup_file,
                       check=True)


def subcommand_restore_database(_):
    """Restore database from file."""
    db_name = _get_db_name()
    subprocess.run(['mysqladmin', '--force', 'drop', db_name], check=False)
    subprocess.run(['mysqladmin', 'create', db_name], check=True)
    with open(DB_BACKUP_FILE, 'r') as db_restore_file:
        subprocess.run(['mysql', db_name], stdin=db_restore_file, check=True)


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