import argparse
import json
import subprocess

from ..completions import (
    add_dynamic_completer,
    complete_backup_providers_for_machine,
    complete_machines,
)
from ..errors import ClanError
from ..machines.machines import Machine


def restore_service(machine: Machine, name: str, provider: str, service: str) -> None:
    backup_metadata = json.loads(machine.eval_nix("config.clan.core.backups"))
    backup_folders = json.loads(machine.eval_nix("config.clan.core.state"))

    if service not in backup_folders:
        msg = f"Service {service} not found in configuration. Available services are: {', '.join(backup_folders.keys())}"
        raise ClanError(msg)

    folders = backup_folders[service]["folders"]
    env = {}
    env["NAME"] = name
    # FIXME: If we have too many folder this might overflow the stack.
    env["FOLDERS"] = ":".join(set(folders))

    if pre_restore := backup_folders[service]["preRestoreCommand"]:
        proc = machine.target_host.run(
            [pre_restore],
            stdout=subprocess.PIPE,
            extra_env=env,
        )
        if proc.returncode != 0:
            raise ClanError(
                f"failed to run preRestoreCommand: {pre_restore}, error was: {proc.stdout}"
            )

    proc = machine.target_host.run(
        [backup_metadata["providers"][provider]["restore"]],
        stdout=subprocess.PIPE,
        extra_env=env,
    )
    if proc.returncode != 0:
        raise ClanError(
            f"failed to restore backup: {backup_metadata['providers'][provider]['restore']}"
        )

    if post_restore := backup_folders[service]["postRestoreCommand"]:
        proc = machine.target_host.run(
            [post_restore],
            stdout=subprocess.PIPE,
            extra_env=env,
        )
        if proc.returncode != 0:
            raise ClanError(
                f"failed to run postRestoreCommand: {post_restore}, error was: {proc.stdout}"
            )


def restore_backup(
    machine: Machine,
    provider: str,
    name: str,
    service: str | None = None,
) -> None:
    errors = []
    if service is None:
        backup_folders = json.loads(machine.eval_nix("config.clan.core.state"))
        for _service in backup_folders:
            try:
                restore_service(machine, name, provider, _service)
            except ClanError as e:
                errors.append(f"{_service}: {e}")
    else:
        try:
            restore_service(machine, name, provider, service)
        except ClanError as e:
            errors.append(f"{service}: {e}")
    if errors:
        raise ClanError(
            "Restore failed for the following services:\n" + "\n".join(errors)
        )


def restore_command(args: argparse.Namespace) -> None:
    if args.flake is None:
        raise ClanError("Could not find clan flake toplevel directory")
    machine = Machine(name=args.machine, flake=args.flake)
    restore_backup(
        machine=machine,
        provider=args.provider,
        name=args.name,
        service=args.service,
    )


def register_restore_parser(parser: argparse.ArgumentParser) -> None:
    machine_action = parser.add_argument(
        "machine", type=str, help="machine in the flake to create backups of"
    )
    add_dynamic_completer(machine_action, complete_machines)
    provider_action = parser.add_argument(
        "provider", type=str, help="backup provider to use"
    )
    add_dynamic_completer(provider_action, complete_backup_providers_for_machine)
    parser.add_argument("name", type=str, help="Name of the backup to restore")
    parser.add_argument("--service", type=str, help="name of the service to restore")
    parser.set_defaults(func=restore_command)
