import argparse
import json
import os
import subprocess
from pathlib import Path

from ..dirs import get_clan_flake_toplevel
from ..machines.machines import Machine
from ..nix import nix_build, nix_command, nix_config
from ..secrets.generate import generate_secrets
from ..secrets.upload import upload_secrets
from ..ssh import Host, HostGroup, HostKeyCheck, parse_deployment_address


def deploy_nixos(hosts: HostGroup, clan_dir: Path) -> None:
    """
    Deploy to all hosts in parallel
    """

    def deploy(h: Host) -> None:
        target = f"{h.user or 'root'}@{h.host}"
        ssh_arg = f"-p {h.port}" if h.port else ""
        env = os.environ.copy()
        env["NIX_SSHOPTS"] = ssh_arg
        res = h.run_local(
            nix_command(["flake", "archive", "--to", f"ssh://{target}", "--json"]),
            check=True,
            stdout=subprocess.PIPE,
            extra_env=env,
        )
        data = json.loads(res.stdout)
        path = data["path"]

        if h.host_key_check != HostKeyCheck.STRICT:
            ssh_arg += " -o StrictHostKeyChecking=no"
        if h.host_key_check == HostKeyCheck.NONE:
            ssh_arg += " -o UserKnownHostsFile=/dev/null"

        ssh_arg += " -i " + h.key if h.key else ""

        flake_attr = h.meta.get("flake_attr", "")

        generate_secrets(h.meta["machine"])
        upload_secrets(h.meta["machine"])

        target_host = h.meta.get("target_host")
        if target_host:
            target_user = h.meta.get("target_user")
            if target_user:
                target_host = f"{target_user}@{target_host}"
        extra_args = h.meta.get("extra_args", [])
        cmd = (
            ["nixos-rebuild", "switch"]
            + extra_args
            + [
                "--fast",
                "--option",
                "keep-going",
                "true",
                "--option",
                "accept-flake-config",
                "true",
                "--build-host",
                "",
                "--flake",
                f"{path}#{flake_attr}",
            ]
        )
        if target_host:
            cmd.extend(["--target-host", target_host])
        ret = h.run(cmd, check=False)
        # re-retry switch if the first time fails
        if ret.returncode != 0:
            ret = h.run(cmd)

    hosts.run_function(deploy)


# function to speedup eval if we want to evauluate all machines
def get_all_machines(clan_dir: Path) -> HostGroup:
    config = nix_config()
    system = config["system"]
    machines_json = subprocess.run(
        nix_build([f'{clan_dir}#clanInternals.all-machines-json."{system}"']),
        stdout=subprocess.PIPE,
        check=True,
        text=True,
    ).stdout

    machines = json.loads(Path(machines_json).read_text())

    hosts = []
    for name, machine_data in machines.items():
        # very hacky. would be better to do a MachinesGroup instead
        host = parse_deployment_address(
            name,
            machine_data["deploymentAddress"],
            meta={"machine": Machine(name=name, machine_data=machine_data)},
        )
        hosts.append(host)
    return HostGroup(hosts)


def get_selected_machines(machine_names: list[str], clan_dir: Path) -> HostGroup:
    hosts = []
    for name in machine_names:
        machine = Machine(name=name, clan_dir=clan_dir)
        hosts.append(machine.host)
    return HostGroup(hosts)


# FIXME: we want some kind of inventory here.
def update(args: argparse.Namespace) -> None:
    clan_dir = get_clan_flake_toplevel()
    if len(args.machines) == 1 and args.target_host is not None:
        machine = Machine(name=args.machines[0], clan_dir=clan_dir)
        machine.deployment_address = args.target_host
        host = parse_deployment_address(
            args.machines[0],
            args.target_host,
            meta={"machine": machine},
        )
        machines = HostGroup([host])

    elif args.target_host is not None:
        print("target host can only be specified for a single machine")
        exit(1)
    else:
        if len(args.machines) == 0:
            machines = get_all_machines(clan_dir)
        else:
            machines = get_selected_machines(args.machines, clan_dir)

    deploy_nixos(machines, clan_dir)


def register_update_parser(parser: argparse.ArgumentParser) -> None:
    parser.add_argument(
        "machines",
        type=str,
        help="machine to update. if empty, update all machines",
        nargs="*",
        default=[],
    )
    parser.add_argument(
        "--target-host",
        type=str,
        help="address of the machine to update, in the format of user@host:1234",
    )
    parser.set_defaults(func=update)
