"""Containerlab export functionality.

Generates Containerlab topology files from calculator results.
"""

from typing import Any


# Containerlab node kinds for different vendors/platforms
CLAB_KINDS = {
    "eos": "ceos",
    "arista": "ceos",
    "nxos": "cisco_nxos",
    "cisco_nxos": "cisco_nxos9000v",
    "iosxr": "cisco_xrd",
    "iosxe": "cisco_csr1000v",
    "junos": "crpd",
    "juniper": "crpd",
    "sonic": "sonic-vs",
    "srlinux": "srl",
    "nokia": "srl",
    "cumulus": "cvx",
    "linux": "linux",
    "vyos": "vyos",
    "frr": "linux",
}

# Default container images for each kind
CLAB_IMAGES = {
    "ceos": "ceos:4.30.4M",
    "cisco_nxos9000v": "vrnetlab/vr-n9kv:9.3.9",
    "cisco_xrd": "ios-xr/xrd-control-plane:7.8.1",
    "cisco_csr1000v": "vrnetlab/vr-csr:17.03.04a",
    "crpd": "crpd:23.2R1.13",
    "sonic-vs": "docker-sonic-vs:latest",
    "srl": "ghcr.io/nokia/srlinux:24.3.2",
    "cvx": "networkop/cx:5.3.0",
    "linux": "alpine:latest",
    "vyos": "vyos/vyos:1.4-rolling",
}


def export_containerlab_topology(
    spines: list[dict[str, Any]],
    leaves: list[dict[str, Any]],
    lab_name: str = "vxlan-fabric",
    platform: str = "eos",
    mgmt_network: str = "172.20.20.0/24",
    include_hosts: bool = False,
    hosts_per_leaf: int = 1,
) -> str:
    """
    Generate Containerlab topology YAML from fabric data.

    Args:
        spines: List of spine switch dicts with name, ip, asn
        leaves: List of leaf switch dicts with name, ip, asn, vtep_ip
        lab_name: Name of the lab
        platform: Target platform (eos, srlinux, sonic, etc.)
        mgmt_network: Management network for lab
        include_hosts: Whether to add test hosts to leaves
        hosts_per_leaf: Number of hosts per leaf if include_hosts is True

    Returns:
        Containerlab topology as YAML string
    """
    kind = CLAB_KINDS.get(platform, platform)
    image = CLAB_IMAGES.get(kind, f"{kind}:latest")

    lines = [
        f"# Containerlab VXLAN/EVPN Fabric Topology",
        f"# Generated by vxlan-calculator",
        f"# Deploy with: containerlab deploy -t {lab_name}.clab.yml",
        "",
        f"name: {lab_name}",
        "",
        "mgmt:",
        f"  network: {lab_name}-mgmt",
        f"  ipv4-subnet: {mgmt_network}",
        "",
        "topology:",
        "  kinds:",
        f"    {kind}:",
        f"      image: {image}",
    ]

    # Add linux kind for hosts if needed
    if include_hosts:
        lines.extend([
            "    linux:",
            "      image: alpine:latest",
        ])

    lines.extend([
        "",
        "  nodes:",
    ])

    # Add spine nodes
    mgmt_ip_base = int(mgmt_network.split('.')[2])
    ip_counter = 1

    for i, spine in enumerate(spines):
        spine_name = spine.get("name", f"spine-{i + 1}")
        spine_ip = f"172.20.{mgmt_ip_base}.{ip_counter}"
        ip_counter += 1

        lines.extend([
            f"    {spine_name}:",
            f"      kind: {kind}",
            f"      mgmt-ipv4: {spine_ip}",
        ])

        # Add startup config or environment variables based on platform
        if kind in ("ceos", "srl"):
            lines.extend([
                "      startup-config: configs/" + spine_name + ".cfg",
            ])
        elif kind == "cvx":
            lines.extend([
                "      runtime: docker",
            ])

        # Add labels for metadata
        lines.extend([
            "      labels:",
            "        role: spine",
            f"        loopback: {spine.get('loopback', spine.get('ip', ''))}",
            f"        asn: \"{spine.get('asn', '')}\"",
            "",
        ])

    # Add leaf nodes
    for i, leaf in enumerate(leaves):
        leaf_name = leaf.get("name", f"leaf-{i + 1}")
        leaf_ip = f"172.20.{mgmt_ip_base}.{ip_counter}"
        ip_counter += 1

        lines.extend([
            f"    {leaf_name}:",
            f"      kind: {kind}",
            f"      mgmt-ipv4: {leaf_ip}",
        ])

        if kind in ("ceos", "srl"):
            lines.extend([
                "      startup-config: configs/" + leaf_name + ".cfg",
            ])
        elif kind == "cvx":
            lines.extend([
                "      runtime: docker",
            ])

        lines.extend([
            "      labels:",
            "        role: leaf",
            f"        loopback: {leaf.get('loopback', leaf.get('ip', ''))}",
            f"        vtep_ip: {leaf.get('vtep_ip', '')}",
            f"        asn: \"{leaf.get('asn', '')}\"",
            "",
        ])

    # Add host nodes if requested
    if include_hosts:
        for i, leaf in enumerate(leaves):
            leaf_name = leaf.get("name", f"leaf-{i + 1}")
            for h in range(hosts_per_leaf):
                host_name = f"host-{i + 1}-{h + 1}" if hosts_per_leaf > 1 else f"host-{i + 1}"
                host_ip = f"172.20.{mgmt_ip_base}.{ip_counter}"
                ip_counter += 1

                lines.extend([
                    f"    {host_name}:",
                    "      kind: linux",
                    f"      mgmt-ipv4: {host_ip}",
                    "      exec:",
                    "        - ip addr add 192.168.10." + str(i * hosts_per_leaf + h + 1) + "/24 dev eth1",
                    "      labels:",
                    "        role: host",
                    f"        connected_to: {leaf_name}",
                    "",
                ])

    # Add links section
    lines.extend([
        "  links:",
        "    # Spine-Leaf Interconnects",
    ])

    # Generate full mesh spine-leaf links
    link_counter = 1
    for i, spine in enumerate(spines):
        spine_name = spine.get("name", f"spine-{i + 1}")
        for j, leaf in enumerate(leaves):
            leaf_name = leaf.get("name", f"leaf-{j + 1}")
            # Use eth interfaces, starting from eth1 (eth0 is typically management)
            spine_port = f"eth{j + 1}"  # Each spine connects to all leaves
            leaf_port = f"eth{i + 1}"   # Each leaf connects to all spines

            lines.extend([
                f"    - endpoints: [\"{spine_name}:{spine_port}\", \"{leaf_name}:{leaf_port}\"]",
            ])
            link_counter += 1

    # Add host links if requested
    if include_hosts:
        lines.extend([
            "",
            "    # Host Connections",
        ])
        for i, leaf in enumerate(leaves):
            leaf_name = leaf.get("name", f"leaf-{i + 1}")
            for h in range(hosts_per_leaf):
                host_name = f"host-{i + 1}-{h + 1}" if hosts_per_leaf > 1 else f"host-{i + 1}"
                # Host connects to leaf on port after spine connections
                leaf_port = f"eth{len(spines) + h + 1}"

                lines.extend([
                    f"    - endpoints: [\"{leaf_name}:{leaf_port}\", \"{host_name}:eth1\"]",
                ])

    return "\n".join(lines)


def generate_containerlab_configs(
    spines: list[dict[str, Any]],
    leaves: list[dict[str, Any]],
    platform: str = "eos",
    bgp_as: int = 65000,
) -> dict[str, str]:
    """
    Generate startup configurations for each node.

    Args:
        spines: List of spine switch dicts
        leaves: List of leaf switch dicts
        platform: Target platform
        bgp_as: BGP AS number

    Returns:
        Dictionary mapping node name to config content
    """
    configs = {}

    if platform in ("eos", "arista"):
        # Generate Arista EOS configs
        for i, spine in enumerate(spines):
            name = spine.get("name", f"spine-{i + 1}")
            loopback = spine.get("loopback", spine.get("ip", f"10.0.0.{i + 1}"))
            asn = spine.get("asn", bgp_as)

            config = f"""! Spine {name} Configuration
hostname {name}
!
service routing protocols model multi-agent
!
interface Loopback0
   ip address {loopback}/32
!
ip routing
!
router bgp {asn}
   router-id {loopback}
   no bgp default ipv4-unicast
   maximum-paths 4
   !
   neighbor LEAVES peer group
   neighbor LEAVES remote-as external
   neighbor LEAVES send-community extended
   !
   address-family ipv4
      neighbor LEAVES activate
   !
   address-family evpn
      neighbor LEAVES activate
!
"""
            configs[name] = config

        for i, leaf in enumerate(leaves):
            name = leaf.get("name", f"leaf-{i + 1}")
            loopback = leaf.get("loopback", leaf.get("ip", f"10.0.0.{len(spines) + i + 1}"))
            vtep_ip = leaf.get("vtep_ip", f"10.0.1.{i + 1}")
            asn = leaf.get("asn", bgp_as + i + 1)

            config = f"""! Leaf {name} Configuration
hostname {name}
!
service routing protocols model multi-agent
!
interface Loopback0
   ip address {loopback}/32
!
interface Loopback1
   description VTEP
   ip address {vtep_ip}/32
!
interface Vxlan1
   vxlan source-interface Loopback1
   vxlan udp-port 4789
!
ip routing
!
router bgp {asn}
   router-id {loopback}
   no bgp default ipv4-unicast
   maximum-paths 4
   !
   neighbor SPINES peer group
   neighbor SPINES remote-as external
   neighbor SPINES send-community extended
   !
   address-family ipv4
      neighbor SPINES activate
   !
   address-family evpn
      neighbor SPINES activate
!
"""
            configs[name] = config

    elif platform in ("srlinux", "nokia"):
        # Generate Nokia SR Linux configs
        for i, spine in enumerate(spines):
            name = spine.get("name", f"spine-{i + 1}")
            loopback = spine.get("loopback", spine.get("ip", f"10.0.0.{i + 1}"))

            config = f"""# Spine {name} SR Linux Configuration
set / system name host-name {name}
set / interface system0 admin-state enable
set / interface system0 subinterface 0 admin-state enable
set / interface system0 subinterface 0 ipv4 address {loopback}/32
"""
            configs[name] = config

        for i, leaf in enumerate(leaves):
            name = leaf.get("name", f"leaf-{i + 1}")
            loopback = leaf.get("loopback", leaf.get("ip", f"10.0.0.{len(spines) + i + 1}"))

            config = f"""# Leaf {name} SR Linux Configuration
set / system name host-name {name}
set / interface system0 admin-state enable
set / interface system0 subinterface 0 admin-state enable
set / interface system0 subinterface 0 ipv4 address {loopback}/32
"""
            configs[name] = config

    return configs


def generate_makefile() -> str:
    """Generate a Makefile for common lab operations."""
    return """# Containerlab VXLAN Fabric Makefile
# Generated by vxlan-calculator

TOPOLOGY ?= topology.clab.yml
LAB_NAME ?= $(shell grep -m1 'name:' $(TOPOLOGY) | awk '{print $$2}')

.PHONY: deploy destroy graph inspect logs

deploy:
\tcontainerlab deploy -t $(TOPOLOGY)

destroy:
\tcontainerlab destroy -t $(TOPOLOGY)

graph:
\tcontainerlab graph -t $(TOPOLOGY)

inspect:
\tcontainerlab inspect -t $(TOPOLOGY)

logs:
\tdocker logs -f clab-$(LAB_NAME)-spine-1

connect-%:
\tdocker exec -it clab-$(LAB_NAME)-$* Cli

shell-%:
\tdocker exec -it clab-$(LAB_NAME)-$* sh

save:
\tcontainerlab save -t $(TOPOLOGY)

clean:
\tcontainerlab destroy -t $(TOPOLOGY) --cleanup

help:
\t@echo "Available targets:"
\t@echo "  deploy    - Deploy the lab"
\t@echo "  destroy   - Destroy the lab"
\t@echo "  graph     - Generate topology graph"
\t@echo "  inspect   - Show lab status"
\t@echo "  logs      - Show spine-1 logs"
\t@echo "  connect-* - Connect to node CLI (e.g., connect-leaf-1)"
\t@echo "  shell-*   - Open shell on node (e.g., shell-host-1)"
\t@echo "  save      - Save running configs"
\t@echo "  clean     - Destroy and cleanup"
"""
