"""Cloud-init NoCloud user-data / meta-data generation (pure renderers).

cloud-init's **NoCloud** datasource reads a two-file seed
(``user-data`` + ``meta-data``) from an attached ISO labelled
``cidata``. VManager generates the two files from a
:class:`ProvisioningInput`, packages them via ``cloud-localds`` (from
the ``cloud-image-utils`` apt package), and hands the resulting ISO
to the builder which attaches it as a second CD-ROM on first boot.

This module is the **pure** half. The dict builders
(:func:`build_user_data` / :func:`build_meta_data`) and the YAML text
renderers (:func:`render_user_data` / :func:`render_meta_data`)
perform no I/O. The impure parts — atomic file writes
(``write_files``), the ``cloud-localds`` subprocess
(``package_seed_iso``), and the end-to-end ``generate`` — are
adapter-bound and remain in the legacy ``mgf.vm.provisioning``
package; they would taint this pure core.

Pure-Python templating is used here instead of Jinja or similar: the
output schema is fixed and small, and avoiding a template engine
keeps the dependency footprint clean. The generated YAML is produced
by ``yaml.safe_dump`` so it round-trips through ``yaml.safe_load``.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

import yaml

if TYPE_CHECKING:
    from mgf.vm.core.provisioning.spec import ProvisioningInput, ProvisioningSpec


def build_user_data(spec: ProvisioningInput) -> dict[str, Any]:
    """Build the cloud-init ``user-data`` payload as a plain dict.

    Only the fields that VManager knows about are emitted; a user who
    needs a knob we do not expose can provide their own ``user-data``
    via ``run_commands`` until we support ``extra_user_data``.
    """
    prov: ProvisioningSpec = spec.provisioning
    payload: dict[str, Any] = {
        "hostname": prov.hostname or spec.name,
        "manage_etc_hosts": True,
    }

    # Default user — only emitted if the spec actually sets one.
    if prov.username:
        user_entry: dict[str, Any] = {
            "name": prov.username,
            "sudo": "ALL=(ALL) NOPASSWD:ALL",
            "shell": "/bin/bash",
            "lock_passwd": False,
        }
        if prov.ssh_authorized_keys:
            user_entry["ssh_authorized_keys"] = list(prov.ssh_authorized_keys)
        if prov.password:
            user_entry["plain_text_passwd"] = prov.password
            # Cloud-init requires this flag to honour a plaintext password.
            payload["ssh_pwauth"] = True
            payload["chpasswd"] = {"expire": False}
        payload["users"] = [user_entry]
    elif prov.ssh_authorized_keys:
        # No explicit username but the user did provide SSH keys — attach
        # them to the default ``ubuntu`` account so the VM is still
        # reachable.
        payload["ssh_authorized_keys"] = list(prov.ssh_authorized_keys)

    if prov.packages:
        payload["package_update"] = True
        payload["packages"] = list(prov.packages)

    if prov.run_commands:
        payload["runcmd"] = list(prov.run_commands)

    return payload


def build_meta_data(spec: ProvisioningInput) -> dict[str, Any]:
    """Build the cloud-init ``meta-data`` payload.

    NoCloud requires ``instance-id`` (unique per boot) and
    ``local-hostname``. We use the VM name as both — it's stable,
    unique inside a host, and visible in ``virsh list``.
    """
    prov = spec.provisioning
    return {
        "instance-id": f"iid-{spec.name}",
        "local-hostname": prov.hostname or spec.name,
    }


def render_user_data(spec: ProvisioningInput) -> str:
    """Return the textual ``user-data`` file content.

    Always starts with ``#cloud-config\\n`` — cloud-init refuses to
    parse a YAML document without this header line.
    """
    payload = build_user_data(spec)
    body = yaml.safe_dump(
        payload,
        sort_keys=False,
        default_flow_style=False,
        allow_unicode=True,
    )
    return "#cloud-config\n" + body


def render_meta_data(spec: ProvisioningInput) -> str:
    """Return the textual ``meta-data`` file content."""
    return yaml.safe_dump(
        build_meta_data(spec),
        sort_keys=False,
        default_flow_style=False,
        allow_unicode=True,
    )


__all__ = [
    "build_meta_data",
    "build_user_data",
    "render_meta_data",
    "render_user_data",
]
