---
# Provision a KVM/libvirt VM from an Ubuntu cloud image.
# Runs on the hypervisor host (localhost) without requiring sudo.
# All writes to /var/lib/libvirt/images/ go through libvirtd (virsh vol-upload).

- name: Validate required variables
  ansible.builtin.assert:
    that:
      - vm_domain | length > 0
      - vm_hostname | length > 0
      - vm_disk_path | length > 0
      - vm_mac | length > 0
      - vm_ip | length > 0
    fail_msg: "vm_domain, vm_hostname, vm_disk_path, vm_mac, and vm_ip must all be set in host_vars"

# ── Cloud image ────────────────────────────────────────────────────────────

- name: Check if cloud image volume exists in pool
  ansible.builtin.command:
    cmd: virsh -c {{ vm_libvirt_uri }} vol-info --pool default {{ vm_cloud_image_cache | basename }}
  register: cloud_image_vol_stat
  failed_when: false
  changed_when: false

- name: Download Ubuntu cloud image to temp path
  ansible.builtin.get_url:
    url: "{{ vm_cloud_image_url }}"
    dest: "/tmp/{{ vm_cloud_image_cache | basename }}"
    mode: "0644"
    timeout: 600
  when: cloud_image_vol_stat.rc != 0

- name: Create cloud image volume in pool
  ansible.builtin.shell:
    cmd: >
      virsh -c {{ vm_libvirt_uri }} vol-create-as default
      '{{ vm_cloud_image_cache | basename }}' 4G --format raw
  register: cloud_vol_create
  failed_when:
    - cloud_vol_create.rc != 0
    - "'exists already' not in cloud_vol_create.stderr"
  changed_when: cloud_vol_create.rc == 0
  when: cloud_image_vol_stat.rc != 0

- name: Upload cloud image to pool
  ansible.builtin.shell:
    cmd: >
      virsh -c {{ vm_libvirt_uri }} vol-upload
      --pool default '{{ vm_cloud_image_cache | basename }}'
      '/tmp/{{ vm_cloud_image_cache | basename }}'
  when: cloud_image_vol_stat.rc != 0 and (cloud_vol_create.rc | default(1)) == 0

- name: Remove temp cloud image download
  ansible.builtin.file:
    path: "/tmp/{{ vm_cloud_image_cache | basename }}"
    state: absent
  when: cloud_image_vol_stat.rc != 0

# ── Disk image ─────────────────────────────────────────────────────────────

- name: Check if VM disk volume exists in pool
  ansible.builtin.shell:
    cmd: virsh -c {{ vm_libvirt_uri }} vol-info --pool default '{{ vm_disk_path | basename }}'
  register: vm_disk_vol_stat
  failed_when: false
  changed_when: false

- name: Create VM disk volume backed by cloud image
  ansible.builtin.shell:
    cmd: >
      virsh -c {{ vm_libvirt_uri }} vol-create-as default
      '{{ vm_disk_path | basename }}' {{ vm_disk_size }} --format qcow2
      --backing-vol '{{ vm_cloud_image_cache | basename }}'
      --backing-vol-format qcow2
  register: disk_vol_create
  failed_when:
    - disk_vol_create.rc != 0
    - "'exists already' not in disk_vol_create.stderr"
  changed_when: disk_vol_create.rc == 0
  when: vm_disk_vol_stat.rc != 0

# ── Cloud-init seed ISO ────────────────────────────────────────────────────

- name: Create cloud-init temp directory
  ansible.builtin.tempfile:
    state: directory
    suffix: cloud-init
  register: cloud_init_dir

- name: Write cloud-init user-data
  ansible.builtin.template:
    src: cloud-init-user-data.j2
    dest: "{{ cloud_init_dir.path }}/user-data"
    mode: "0644"
  vars:
    vm_ssh_keys: "{{ openclaw_ssh_keys | default([]) }}"

- name: Write cloud-init meta-data
  ansible.builtin.template:
    src: cloud-init-meta-data.j2
    dest: "{{ cloud_init_dir.path }}/meta-data"
    mode: "0644"

- name: Set seed ISO facts
  ansible.builtin.set_fact:
    vm_seed_iso_name: "{{ vm_hostname }}-seed.iso"
    vm_seed_iso_tmp: "/tmp/{{ vm_hostname }}-seed.iso"
    vm_seed_iso: "/var/lib/libvirt/images/{{ vm_hostname }}-seed.iso"

- name: Create cloud-init seed ISO in temp path
  ansible.builtin.command:
    cmd: >
      genisoimage -output {{ vm_seed_iso_tmp }}
      -volid cidata -joliet -rock
      {{ cloud_init_dir.path }}/user-data
      {{ cloud_init_dir.path }}/meta-data
  changed_when: true

- name: Create seed ISO volume in pool
  ansible.builtin.shell:
    cmd: >
      virsh -c {{ vm_libvirt_uri }} vol-create-as default
      '{{ vm_seed_iso_name }}' 4M --format raw
  register: seed_vol_create
  failed_when:
    - seed_vol_create.rc != 0
    - "'exists already' not in seed_vol_create.stderr"
  changed_when: seed_vol_create.rc == 0

- name: Upload seed ISO to pool
  ansible.builtin.shell:
    cmd: >
      virsh -c {{ vm_libvirt_uri }} vol-upload
      --pool default '{{ vm_seed_iso_name }}' '{{ vm_seed_iso_tmp }}'
  changed_when: true

- name: Clean up cloud-init temp files
  ansible.builtin.file:
    path: "{{ item }}"
    state: absent
  loop:
    - "{{ cloud_init_dir.path }}"
    - "{{ vm_seed_iso_tmp }}"

# ── VM definition ──────────────────────────────────────────────────────────

- name: Check if VM domain already exists
  community.libvirt.virt:
    command: list_vms
    uri: "{{ vm_libvirt_uri }}"
  register: existing_vms

- name: Define VM from XML template
  community.libvirt.virt:
    command: define
    xml: "{{ lookup('template', 'domain.xml.j2') }}"
    uri: "{{ vm_libvirt_uri }}"
  when: vm_domain not in existing_vms.list_vms

# ── Network ────────────────────────────────────────────────────────────────

- name: Add static DHCP reservation
  ansible.builtin.command:
    cmd: >
      virsh -c {{ vm_libvirt_uri }} net-update {{ vm_network }}
      add ip-dhcp-host
      '<host mac="{{ vm_mac }}" name="{{ vm_hostname }}" ip="{{ vm_ip }}"/>'
      --live --config
  register: dhcp_result
  failed_when:
    - dhcp_result.rc != 0
    - "'existing dhcp host entry' not in dhcp_result.stderr"
    - "'already exists' not in dhcp_result.stderr"
  changed_when: dhcp_result.rc == 0

# ── Autostart & boot ───────────────────────────────────────────────────────

- name: Enable autostart
  community.libvirt.virt:
    name: "{{ vm_domain }}"
    autostart: true
    uri: "{{ vm_libvirt_uri }}"

- name: Start VM
  community.libvirt.virt:
    name: "{{ vm_domain }}"
    state: running
    uri: "{{ vm_libvirt_uri }}"

- name: Wait for SSH to become available
  ansible.builtin.wait_for:
    host: "{{ vm_ip }}"
    port: 22
    delay: 10
    timeout: 300
    state: started
  delegate_to: localhost

- name: VM is ready
  ansible.builtin.debug:
    msg: "VM '{{ vm_domain }}' is up at {{ vm_ip }}. Run install.yml + customize.yml to provision the guest."
