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

import argparse
import os
import pathlib
import random
import shutil
import string
import subprocess

import augeas

from plinth import action_utils
from plinth.modules.wordpress import PUBLIC_ACCESS_FILE

_config_file_path = pathlib.Path('/etc/wordpress/config-default.php')
_db_file_path = pathlib.Path('/etc/wordpress/database.php')
_db_backup_file = pathlib.Path(
    '/var/lib/plinth/backups-data/wordpress-database.sql')
DB_HOST = 'localhost'
DB_NAME = 'wordpress_fbx'
DB_USER = 'wordpress_fbx'


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('setup',
                          help='Create initial configuration and database')
    subparsers.add_parser('dump-database', help='Dump database to file')
    subparsers.add_parser('restore-database',
                          help='Restore database from file')
    subparser = subparsers.add_parser('set-public',
                                      help='Allow/disallow public access')
    subparser.add_argument('--enable', choices=('True', 'False'),
                           help='Whether to enable or disable public acceess')

    subparsers.required = True
    return parser.parse_args()


def subcommand_setup(_):
    """Create initial configuration and database for WordPress."""
    if _db_file_path.exists() or _config_file_path.exists():
        return

    db_password = _generate_secret_key(16)

    _create_config_file(DB_HOST, DB_NAME, DB_USER, db_password)
    _create_database(DB_NAME)
    _set_privileges(DB_HOST, DB_NAME, DB_USER, db_password)


def _create_config_file(db_host, db_name, db_user, db_password):
    """Create a PHP configuration file included by WordPress."""
    secret_keys = [_generate_secret_key() for _ in range(8)]

    config_contents = f'''<?php
# Created by FreedomBox
include_once('{_db_file_path}');
define('DB_NAME', $dbname);
define('DB_USER', $dbuser);
define('DB_PASSWORD', $dbpass);
define('DB_HOST', $dbserver);

define('AUTH_KEY', '{secret_keys[0]}');
define('SECURE_AUTH_KEY', '{secret_keys[1]}');
define('LOGGED_IN_KEY', '{secret_keys[2]}');
define('NONCE_KEY', '{secret_keys[3]}');
define('AUTH_SALT', '{secret_keys[4]}');
define('SECURE_AUTH_SALT', '{secret_keys[5]}');
define('LOGGED_IN_SALT', '{secret_keys[6]}');
define('NONCE_SALT', '{secret_keys[7]}');

define('WP_CONTENT_DIR', '/var/lib/wordpress/wp-content');

define('DISABLE_WP_CRON', true);
'''
    _config_file_path.write_text(config_contents)

    db_contents = f'''<?php
# Created by FreedomBox
$dbuser='{db_user}';
$dbpass='{db_password}';
$dbname='{db_name}';
$dbserver='{db_host}';
'''
    old_umask = os.umask(0o037)
    try:
        _db_file_path.write_text(db_contents)
    finally:
        os.umask(old_umask)

    shutil.chown(_db_file_path, group='www-data')


def _create_database(db_name):
    """Create an empty MySQL database for WordPress."""
    # Wordpress' install.php creates the tables.
    # SQL injection is avoided due to known input.
    query = f'''CREATE DATABASE {db_name};'''
    subprocess.run(['mysql', '--user', 'root'], input=query.encode(),
                   check=True)


def _set_privileges(db_host, db_name, db_user, db_password):
    """Create user, set password and provide permissions on the database."""
    # SQL injection is avoided due to known input.
    query = f'''GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER
  ON {db_name}.*
  TO {db_user}@{db_host}
  IDENTIFIED BY '{db_password}';
FLUSH PRIVILEGES;
'''
    subprocess.run(['mysql', '--user', 'root'], input=query.encode(),
                   check=True)


def _generate_secret_key(length=64, chars=None):
    """Generate a new random secret key for use with WordPress."""
    chars = chars or (string.ascii_letters + string.digits +
                      '!@#$%^&*()-_ []{}<>~`+=,.:/?|')
    rand = random.SystemRandom()
    return ''.join(rand.choice(chars) for _ in range(length))


def subcommand_set_public(arguments):
    """Allow/disallow public access."""
    public_access_file = pathlib.Path(PUBLIC_ACCESS_FILE)
    if arguments.enable == 'True':
        public_access_file.touch()
    else:
        public_access_file.unlink(missing_ok=True)

    action_utils.service_reload('apache2')


def subcommand_dump_database(_):
    """Dump database to file."""
    _db_backup_file.parent.mkdir(parents=True, exist_ok=True)
    with _db_backup_file.open('w') as file_handle:
        subprocess.run([
            'mysqldump', '--add-drop-database', '--add-drop-table',
            '--add-drop-trigger', '--user', 'root', '--databases', DB_NAME
        ], stdout=file_handle, check=True)


def subcommand_restore_database(_):
    """Restore database from file."""
    with _db_backup_file.open('r') as file_handle:
        subprocess.run(['mysql', '--user', 'root'], stdin=file_handle,
                       check=True)

    _set_privileges(DB_HOST, DB_NAME, DB_USER, _read_db_password())


def _read_db_password():
    """Return the password stored in the DB configuration file."""
    aug = _load_augeas()
    return aug.get('./$dbpass').strip('\'"')


def _load_augeas():
    """Initialize augeas."""
    aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
                        augeas.Augeas.NO_MODL_AUTOLOAD)
    aug.transform('Phpvars', str(_db_file_path))
    aug.set('/augeas/context', '/files' + str(_db_file_path))
    aug.load()

    return aug


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