#!/usr/bin/env python3
"""Run Python / Ansible-uri / Terraform-data / Pulumi-style cookbooks against the simulator.

Uses only ``requests`` so it works inside the Compose ``dev`` image.
Terraform/Ansible CLIs are optional — when present they are invoked too.
"""

from __future__ import annotations

import json
import os
import shutil
import subprocess
import sys
import tempfile
import urllib3
from pathlib import Path

import requests

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

BASE = os.environ.get("VSPHERE_BASE", "https://localhost").rstrip("/")
USER = os.environ.get("VSPHERE_USER", "administrator@vsphere.local")
PASSWORD = os.environ.get("VSPHERE_PASSWORD", "VMware1!")
ROOT = Path(__file__).resolve().parents[1]


def _session() -> dict[str, str]:
    response = requests.post(f"{BASE}/api/session", auth=(USER, PASSWORD), verify=False, timeout=60)
    response.raise_for_status()
    return {"vmware-api-session-id": response.json()}


def run_python_lifecycle(headers: dict[str, str]) -> dict[str, str]:
    created = requests.post(
        f"{BASE}/api/vcenter/vm",
        headers=headers,
        json={
            "name": "cookbook-py-01",
            "guest_OS": "OTHER_GUEST_64",
            "placement": {
                "folder": "group-v23",
                "host": "host-11",
                "datastore": "datastore-31",
                "resource_pool": "resgroup-22",
            },
            "cpu": {"count": 1},
            "memory": {"size_MiB": 512},
        },
        verify=False,
        timeout=60,
    )
    created.raise_for_status()
    vm = created.json()
    power = requests.post(
        f"{BASE}/api/vcenter/vm/{vm}/power",
        params={"action": "start"},
        headers=headers,
        verify=False,
        timeout=60,
    )
    power.raise_for_status()
    assert power.json().get("task")
    # Platform surfaces previously deferred
    providers = requests.get(
        f"{BASE}/api/vcenter/identity/providers", headers=headers, verify=False, timeout=60
    )
    providers.raise_for_status()
    assert any(p.get("type_id") in {"Oidc", "Saml", "LocalOS"} for p in providers.json())
    nsx = requests.get(
        f"{BASE}/api/vcenter/namespace-management/nsx-tier0-gateway",
        headers=headers,
        verify=False,
        timeout=60,
    )
    nsx.raise_for_status()
    assert nsx.json()
    nfc = requests.post(
        f"{BASE}/sdk",
        data="""<?xml version="1.0"?>
        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
          <soapenv:Body>
            <ImportVApp_Task xmlns="urn:vim25">
              <_this type="Folder">group-v23</_this>
              <name>nfc-import-lab</name>
            </ImportVApp_Task>
          </soapenv:Body>
        </soapenv:Envelope>""",
        headers={
            **headers,
            "Content-Type": "text/xml",
            "Cookie": f'vmware_soap_session="{headers["vmware-api-session-id"]}"',
        },
        verify=False,
        timeout=60,
    )
    nfc.raise_for_status()
    assert "task-" in nfc.text and "ImportVApp_TaskResponse" in nfc.text
    requests.post(
        f"{BASE}/api/vcenter/vm/{vm}/power",
        params={"action": "stop"},
        headers=headers,
        verify=False,
        timeout=60,
    ).raise_for_status()
    requests.delete(
        f"{BASE}/api/vcenter/vm/{vm}", headers=headers, verify=False, timeout=60
    ).raise_for_status()
    return {"python": "ok", "vm": str(vm)}


def run_ansible_uri(headers: dict[str, str]) -> dict[str, str]:
    """Mirror examples/ansible/vsphere_playbook.yml using the same REST calls."""

    created = requests.post(
        f"{BASE}/api/vcenter/vm",
        headers=headers,
        json={
            "name": "cookbook-ansible-01",
            "guest_OS": "OTHER_GUEST_64",
            "placement": {
                "folder": "group-v23",
                "host": "host-11",
                "datastore": "datastore-31",
                "resource_pool": "resgroup-22",
            },
            "cpu": {"count": 1},
            "memory": {"size_MiB": 512},
        },
        verify=False,
        timeout=60,
    )
    created.raise_for_status()
    vm = created.json()
    for action in ("start", "stop"):
        requests.post(
            f"{BASE}/api/vcenter/vm/{vm}/power",
            params={"action": action},
            headers=headers,
            verify=False,
            timeout=60,
        ).raise_for_status()
    requests.put(
        f"{BASE}/api/vcenter/vm/{vm}/guest/filesystem",
        params={"path": "/tmp/ansible-marker"},
        headers=headers,
        json={"content": "ansible-ok"},
        verify=False,
        timeout=60,
    ).raise_for_status()
    requests.delete(
        f"{BASE}/api/vcenter/vm/{vm}", headers=headers, verify=False, timeout=60
    ).raise_for_status()
    result = {"ansible_uri": "ok", "vm": str(vm)}
    playbook = ROOT / "examples" / "ansible" / "vsphere_playbook.yml"
    if shutil.which("ansible-playbook") and playbook.is_file():
        # Prefer simulator HTTP inside compose if BASE is internal.
        env_base = BASE.replace("https://localhost", "https://localhost")
        proc = subprocess.run(
            [
                "ansible-playbook",
                "-i",
                str(ROOT / "examples" / "ansible" / "inventory.ini"),
                str(playbook),
                "-e",
                f"vsphere_base={env_base}",
                "-e",
                "vm_name=cookbook-ansible-cli-01",
            ],
            check=False,
            capture_output=True,
            text=True,
            timeout=180,
        )
        result["ansible_cli"] = "ok" if proc.returncode == 0 else f"failed:{proc.returncode}"
        if proc.returncode != 0:
            result["ansible_cli_stderr"] = (proc.stderr or proc.stdout)[-500:]
    else:
        result["ansible_cli"] = "skipped"
    return result


def run_pulumi_style(headers: dict[str, str]) -> dict[str, str]:
    """Mirror examples/pulumi/__main__.py REST ComponentResource flow."""

    created = requests.post(
        f"{BASE}/api/vcenter/vm",
        headers=headers,
        json={
            "name": "cookbook-pulumi-01",
            "guest_OS": "OTHER_GUEST_64",
            "placement": {
                "folder": "group-v23",
                "host": "host-11",
                "datastore": "datastore-31",
                "resource_pool": "resgroup-22",
            },
            "cpu": {"count": 1},
            "memory": {"size_MiB": 512},
        },
        verify=False,
        timeout=60,
    )
    created.raise_for_status()
    vm = created.json()
    power = requests.post(
        f"{BASE}/api/vcenter/vm/{vm}/power",
        params={"action": "start"},
        headers=headers,
        verify=False,
        timeout=60,
    )
    power.raise_for_status()
    detail = requests.get(f"{BASE}/api/vcenter/vm/{vm}", headers=headers, verify=False, timeout=60)
    detail.raise_for_status()
    requests.post(
        f"{BASE}/api/vcenter/vm/{vm}/power",
        params={"action": "stop"},
        headers=headers,
        verify=False,
        timeout=60,
    ).raise_for_status()
    requests.delete(
        f"{BASE}/api/vcenter/vm/{vm}", headers=headers, verify=False, timeout=60
    ).raise_for_status()
    result = {"pulumi_style": "ok", "vm": str(vm), "name": detail.json().get("name")}
    if shutil.which("pulumi"):
        result["pulumi_cli"] = "available"
    else:
        result["pulumi_cli"] = "skipped"
    return result


def run_terraform_style(headers: dict[str, str]) -> dict[str, str]:
    """Validate the inventory lookups Terraform data sources need (SOAP+REST)."""

    # REST inventory used by many TF plans as complementary checks
    for path in (
        "/api/vcenter/datacenter",
        "/api/vcenter/cluster",
        "/api/vcenter/datastore",
        "/api/vcenter/network",
        "/api/vcenter/vm?names=web-01",
    ):
        response = requests.get(f"{BASE}{path}", headers=headers, verify=False, timeout=60)
        response.raise_for_status()
        assert response.json(), path

    # SOAP FindByInventoryPath + CreateVM (resource path)
    sid = headers["vmware-api-session-id"]
    soap_headers = {
        **headers,
        "Content-Type": "text/xml",
        "Cookie": f'vmware_soap_session="{sid}"',
    }
    find = requests.post(
        f"{BASE}/sdk",
        data="""<?xml version="1.0"?>
        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
          <soapenv:Body>
            <FindByInventoryPath xmlns="urn:vim25">
              <_this type="SearchIndex">SearchIndex</_this>
              <inventoryPath>/Datacenters/Datacenter/vm/web-01</inventoryPath>
            </FindByInventoryPath>
          </soapenv:Body>
        </soapenv:Envelope>""",
        headers=soap_headers,
        verify=False,
        timeout=60,
    )
    find.raise_for_status()
    assert "VirtualMachine" in find.text
    create = requests.post(
        f"{BASE}/sdk",
        data="""<?xml version="1.0"?>
        <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
          <soapenv:Body>
            <CreateVM_Task xmlns="urn:vim25">
              <_this type="Folder">group-v23</_this>
              <config>
                <name>cookbook-tf-01</name>
                <guestId>otherGuest64</guestId>
                <numCPUs>1</numCPUs>
                <memoryMB>512</memoryMB>
                <files><vmPathName>[datastore1]</vmPathName></files>
              </config>
              <pool type="ResourcePool">resgroup-22</pool>
              <host type="HostSystem">host-11</host>
            </CreateVM_Task>
          </soapenv:Body>
        </soapenv:Envelope>""",
        headers=soap_headers,
        verify=False,
        timeout=60,
    )
    create.raise_for_status()
    assert "task-" in create.text
    result = {"terraform_style": "ok"}
    tf_dir = ROOT / "examples" / "terraform" / "vsphere"
    tf_bin = shutil.which("terraform") or (
        str(ROOT / ".tools" / "terraform") if (ROOT / ".tools" / "terraform").is_file() else None
    )
    if tf_bin and tf_dir.is_dir():
        server = BASE.replace("https://", "").replace("http://", "")
        # Prefer the checked-out example (keeps .terraform providers) when writable.
        work = tf_dir if (tf_dir / ".terraform").is_dir() else None
        tmp_ctx = None
        if work is None:
            tmp_ctx = tempfile.TemporaryDirectory()
            work = Path(tmp_ctx.name)
            for name in ("main.tf", "variables.tf"):
                (work / name).write_text(
                    (tf_dir / name).read_text(encoding="utf-8"), encoding="utf-8"
                )
        env = {
            **os.environ,
            "TF_VAR_vsphere_server": server,
            "TF_VAR_vsphere_user": USER,
            "TF_VAR_vsphere_password": PASSWORD,
            "TF_VAR_create_lab_vm": "false",
        }
        try:
            if not (work / ".terraform").is_dir():
                init = subprocess.run(
                    [tf_bin, "init", "-input=false", "-no-color"],
                    cwd=work,
                    env=env,
                    capture_output=True,
                    text=True,
                    timeout=180,
                    check=False,
                )
                if init.returncode != 0:
                    result["terraform_cli"] = f"init_failed:{(init.stderr or '')[-300:]}"
                    return result
            plan = subprocess.run(
                [tf_bin, "plan", "-input=false", "-no-color", "-detailed-exitcode"],
                cwd=work,
                env=env,
                capture_output=True,
                text=True,
                timeout=180,
                check=False,
            )
            # 0 = no changes, 2 = changes present — both OK for data sources
            result["terraform_cli"] = (
                "ok" if plan.returncode in {0, 2} else f"plan_failed:{plan.returncode}"
            )
            if plan.returncode not in {0, 2}:
                result["terraform_cli_stderr"] = ((plan.stderr or "") + (plan.stdout or ""))[-500:]
        finally:
            if tmp_ctx is not None:
                tmp_ctx.cleanup()
    else:
        result["terraform_cli"] = "skipped"
    return result


def main() -> int:
    headers = _session()
    report: dict[str, object] = {"base": BASE}
    failed = False
    for name, fn in (
        ("python", run_python_lifecycle),
        ("ansible", run_ansible_uri),
        ("pulumi", run_pulumi_style),
        ("terraform", run_terraform_style),
    ):
        try:
            report[name] = fn(headers)
        except Exception as error:  # noqa: BLE001
            report[name] = {"error": str(error)}
            failed = True
    print(json.dumps(report, indent=2))
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(main())
