"""Tests for vmcluster_mcp.topology — schema validation and YAML parser."""

import pytest
from pydantic import ValidationError

from vmcluster_mcp.topology.parser import load_topology
from vmcluster_mcp.topology.schema import (
    NetworkSpec,
    NodeSpec,
    SnapshotDefaults,
    SshSpec,
    TopologySpec,
)

VALID_TOPOLOGY_YAML = """\
cluster_name: test-cluster
base_image: /var/lib/vmcluster/images/base.qcow2
overlay_dir: /var/lib/vmcluster/overlays/
network:
  name: testnet
  bridge: virbr-test0
  subnet: 192.168.100.0/24
  dhcp: false
  rdma: false
nodes:
  - name: controller
    role: control
    vcpus: 2
    memory_mb: 2048
    ip: 192.168.100.10
  - name: worker-0
    role: worker
    vcpus: 2
    memory_mb: 2048
    ip: 192.168.100.11
ssh:
  key_path: /etc/vmcluster/ssh/test_key
  user: root
  connect_timeout_s: 30
snapshots:
  baseline: clean-boot
"""


class TestTopologySchema:
    def test_full_topology_validates(self):
        spec = TopologySpec(
            cluster_name="test",
            base_image="/img/base.qcow2",
            overlay_dir="/overlays/",
            network=NetworkSpec(name="net", bridge="br0", subnet="10.0.0.0/24"),
            nodes=[NodeSpec(name="n1", role="worker", vcpus=2, memory_mb=1024, ip="10.0.0.1")],
            ssh=SshSpec(key_path="/key"),
        )
        assert spec.cluster_name == "test"
        assert len(spec.nodes) == 1

    def test_rejects_missing_cluster_name(self):
        with pytest.raises(ValidationError, match="cluster_name"):
            TopologySpec(
                base_image="/img.qcow2",
                overlay_dir="/o/",
                network=NetworkSpec(name="n", bridge="b", subnet="10.0.0.0/24"),
                nodes=[NodeSpec(name="n", role="r", vcpus=1, memory_mb=512, ip="10.0.0.1")],
                ssh=SshSpec(key_path="/k"),
            )

    def test_rejects_missing_nodes(self):
        with pytest.raises(ValidationError, match="nodes"):
            TopologySpec(
                cluster_name="c",
                base_image="/img.qcow2",
                overlay_dir="/o/",
                network=NetworkSpec(name="n", bridge="b", subnet="10.0.0.0/24"),
                ssh=SshSpec(key_path="/k"),
            )

    def test_rejects_node_missing_ip(self):
        with pytest.raises(ValidationError, match="ip"):
            NodeSpec(name="n", role="r", vcpus=1, memory_mb=512)

    def test_rejects_invalid_memory_mb_type(self):
        with pytest.raises(ValidationError):
            NodeSpec(name="n", role="r", vcpus=1, memory_mb="abc", ip="10.0.0.1")

    def test_network_defaults(self):
        ns = NetworkSpec(name="n", bridge="b", subnet="10.0.0.0/24")
        assert ns.dhcp is False
        assert ns.rdma is False

    def test_node_extra_disks_default(self):
        n = NodeSpec(name="n", role="r", vcpus=1, memory_mb=512, ip="10.0.0.1")
        assert n.extra_disks == []

    def test_ssh_defaults(self):
        s = SshSpec(key_path="/k")
        assert s.user == "root"
        assert s.connect_timeout_s == 30

    def test_snapshot_defaults(self):
        sd = SnapshotDefaults()
        assert sd.baseline is None


class TestLoadTopology:
    def test_loads_valid_yaml(self, tmp_path):
        topo_dir = tmp_path / "topologies"
        topo_dir.mkdir()
        (topo_dir / "test-cluster.yaml").write_text(VALID_TOPOLOGY_YAML)

        spec = load_topology("test-cluster", str(topo_dir))
        assert spec.cluster_name == "test-cluster"
        assert len(spec.nodes) == 2
        assert spec.nodes[0].name == "controller"
        assert spec.network.subnet == "192.168.100.0/24"
        assert spec.ssh.key_path == "/etc/vmcluster/ssh/test_key"
        assert spec.snapshots.baseline == "clean-boot"

    def test_file_not_found(self, tmp_path):
        with pytest.raises(FileNotFoundError, match="not found"):
            load_topology("nonexistent", str(tmp_path))

    def test_invalid_field_raises_value_error(self, tmp_path):
        topo_dir = tmp_path / "topologies"
        topo_dir.mkdir()
        (topo_dir / "bad.yaml").write_text(
            "base_image: /img.qcow2\n"
            "overlay_dir: /o/\n"
            "network:\n  name: n\n  bridge: b\n  subnet: s\n"
            "nodes:\n  - name: n1\n    role: r\n    vcpus: 1\n    memory_mb: 512\n    ip: 10.0.0.1\n"
            "ssh:\n  key_path: /k\n"
        )
        with pytest.raises(ValueError, match="cluster_name"):
            load_topology("bad", str(topo_dir))

    def test_wrong_type_raises_value_error(self, tmp_path):
        topo_dir = tmp_path / "topologies"
        topo_dir.mkdir()
        (topo_dir / "badtype.yaml").write_text(
            "cluster_name: c\n"
            "base_image: /img.qcow2\n"
            "overlay_dir: /o/\n"
            "network:\n  name: n\n  bridge: b\n  subnet: s\n"
            "nodes:\n  - name: n1\n    role: r\n    vcpus: 1\n    memory_mb: notanint\n    ip: 10.0.0.1\n"
            "ssh:\n  key_path: /k\n"
        )
        with pytest.raises(ValueError, match="memory_mb"):
            load_topology("badtype", str(topo_dir))

    def test_non_dict_yaml_raises_value_error(self, tmp_path):
        topo_dir = tmp_path / "topologies"
        topo_dir.mkdir()
        (topo_dir / "listfile.yaml").write_text("- item1\n- item2\n")
        with pytest.raises(ValueError, match="YAML mapping"):
            load_topology("listfile", str(topo_dir))
