# SPDX-License-Identifier: AGPL-3.0-or-later
"""Configuration helper for Miniflux feed reader."""

import json
import os
import pathlib
import shutil
from typing import Tuple
from urllib.parse import urlparse

import pexpect

from plinth import action_utils, db
from plinth.actions import privileged, secret_str
from plinth.utils import is_non_empty_file

STATIC_SETTINGS = {
    'BASE_URL': 'http://localhost/miniflux/',
    'RUN_MIGRATIONS': 1,
    'PORT': 8788
}

ENV_VARS_FILE = '/etc/miniflux/freedombox.conf'
DATABASE_FILE = '/etc/miniflux/database'
DB_BACKUP_FILE = '/var/lib/plinth/backups-data/miniflux-database.sql'


def _dict_to_env_file(dictionary: dict[str, str]) -> str:
    """Write a dictionary into a systemd environment file format."""
    return "\n".join((f"{k}={v}" for k, v in dictionary.items()))


def _env_file_to_dict(env_vars: str) -> dict[str, str]:
    """Return systemd environtment variables as a dictionary."""
    return {
        line.split('=')[0]: line.split('=')[1].strip()
        for line in env_vars.splitlines()
        if line.strip() and not line.strip().startswith('#')
    }


@privileged
def pre_setup():
    """Perform pre-install actions for Miniflux."""
    vars_file = pathlib.Path(ENV_VARS_FILE)
    vars_file.parent.mkdir(parents=True, exist_ok=True)

    existing_settings = {}
    if is_non_empty_file(ENV_VARS_FILE):
        # Any comments in the file will be dropped.
        existing_settings = _env_file_to_dict(vars_file.read_text())

    new_settings = existing_settings | STATIC_SETTINGS
    vars_file.write_text(_dict_to_env_file(new_settings))


@privileged
def setup(old_version: int):
    """Perform post-install actions for Miniflux."""
    # Fix incorrect permissions on database file in version 2.2.0-2. See
    # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1081562 . Can be
    # removed after the fix for the bug reaches Trixie/testing.
    shutil.chown(DATABASE_FILE, user='miniflux', group='root')
    if not old_version or action_utils.service_is_enabled('miniflux'):
        # If the service was tried too many times already, it won't
        # successfully restart.
        action_utils.service_reset_failed('miniflux')
        action_utils.service_restart('miniflux')


def _run_miniflux_interactively(command: str, username: str,
                                password: str) -> Tuple[str, dict]:
    """Fill interactive terminal prompt for username and password."""
    args = ['-c', '/etc/miniflux/miniflux.conf', command]
    os.environ['LOG_FORMAT'] = 'json'
    child = pexpect.spawn('miniflux', args, env=os.environ)

    # The CLI is in English only.
    child.expect('Enter Username: ')
    child.sendline(username)

    child.expect('Enter Password: ')
    child.sendline(password)

    child.expect(pexpect.EOF)
    raw_message = child.before.decode()
    try:
        json_message = json.loads(raw_message)
    except (KeyError, json.JSONDecodeError):
        json_message = {}

    child.close()
    if child.exitstatus or child.signalstatus:
        message = json_message.get('msg') if json_message else raw_message
        raise Exception(message)

    return raw_message, json_message


@privileged
def create_admin_user(username: str, password: secret_str):
    """Create a new admin user for Miniflux CLI.

    Raise exception if a user with the name already exists or otherwise fails.
    """
    _, json_message = _run_miniflux_interactively('--create-admin', username,
                                                  password)
    # user_id is allocated only when a new user is created successfully.
    if json_message and not json_message.get('user_id'):
        raise Exception(json_message.get('msg'))


@privileged
def reset_user_password(username: str, password: secret_str):
    """Reset a user password using Miniflux CLI.

    Raise exception if the user does not exist or otherwise fails.
    """
    _run_miniflux_interactively('--reset-password', username, password)


@privileged
def uninstall():
    """Ensure that the database is removed."""
    action_utils.debconf_set_selections(
        ['miniflux miniflux/purge boolean true'])


def _get_database_config():
    """Retrieve database credentials."""
    db_connection_string = pathlib.Path(DATABASE_FILE).read_text().strip()
    parsed_url = urlparse(db_connection_string)
    return {
        'user': parsed_url.username,
        'password': parsed_url.password,
        'database': parsed_url.path.lstrip('/'),
        'host': parsed_url.hostname,
    }


@privileged
def dump_database():
    """Dump database to file."""
    config = _get_database_config()
    db.postgres_dump_database(DB_BACKUP_FILE, config['database'],
                              config['user'])


@privileged
def restore_database():
    """Restore database from file."""
    config = _get_database_config()
    db.postgres_restore_database(DB_BACKUP_FILE, config['database'])
