"""Docker Compose parser — extract services from compose files."""

from __future__ import annotations

from pathlib import Path

import yaml

from ..models import InfraDiagram, InfraEdge, InfraGroup, InfraNode, NodeType, SourceType

# Map common image names to node types
_IMAGE_TYPE_MAP: dict[str, NodeType] = {
    "postgres": NodeType.DATABASE,
    "mysql": NodeType.DATABASE,
    "mariadb": NodeType.DATABASE,
    "mongo": NodeType.DATABASE,
    "redis": NodeType.CACHE,
    "memcached": NodeType.CACHE,
    "rabbitmq": NodeType.QUEUE,
    "kafka": NodeType.QUEUE,
    "nats": NodeType.QUEUE,
    "nginx": NodeType.LOAD_BALANCER,
    "traefik": NodeType.LOAD_BALANCER,
    "haproxy": NodeType.LOAD_BALANCER,
    "caddy": NodeType.LOAD_BALANCER,
    "envoy": NodeType.LOAD_BALANCER,
    "elasticsearch": NodeType.DATABASE,
    "kibana": NodeType.MONITORING,
    "grafana": NodeType.MONITORING,
    "prometheus": NodeType.MONITORING,
    "minio": NodeType.STORAGE,
    "vault": NodeType.SECRET,
    "consul": NodeType.CONFIG,
    "etcd": NodeType.CONFIG,
    "zookeeper": NodeType.CONFIG,
}


def parse_compose(path: str) -> InfraDiagram:
    """Parse Docker Compose file."""
    diagram = InfraDiagram(
        title="Docker Compose Architecture",
        source_type=SourceType.DOCKER_COMPOSE,
    )

    p = Path(path)
    compose_files = []

    if p.is_file():
        compose_files = [p]
    elif p.is_dir():
        for name in ("docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"):
            f = p / name
            if f.exists():
                compose_files.append(f)

    for cf in compose_files:
        diagram.source_files.append(str(cf))
        _parse_compose_file(cf, diagram)

    return diagram


def _parse_compose_file(compose_file: Path, diagram: InfraDiagram) -> None:
    """Parse a single docker-compose file."""
    try:
        content = compose_file.read_text(encoding="utf-8", errors="ignore")
        data = yaml.safe_load(content)
    except Exception:
        return

    if not isinstance(data, dict):
        return

    services = data.get("services", {}) or {}
    volumes = data.get("volumes", {}) or {}
    networks = data.get("networks", {}) or {}

    # Add network nodes
    for net_name in networks:
        diagram.add_node(InfraNode(
            id=f"network/{net_name}",
            name=net_name,
            node_type=NodeType.NETWORK,
            provider="docker",
            source_file=str(compose_file),
        ))

    # Add volume nodes
    for vol_name in volumes:
        diagram.add_node(InfraNode(
            id=f"volume/{vol_name}",
            name=vol_name,
            node_type=NodeType.VOLUME,
            provider="docker",
            source_file=str(compose_file),
        ))

    # Add service nodes
    service_ids = []
    for svc_name, svc_config in services.items():
        if not isinstance(svc_config, dict):
            continue

        image = svc_config.get("image", "")
        node_type = _infer_type_from_image(image) if image else NodeType.CONTAINER

        props: dict = {}
        if image:
            props["image"] = image

        ports = svc_config.get("ports", [])
        if ports:
            props["ports"] = [str(p) for p in ports]

        env = svc_config.get("environment", {})
        if isinstance(env, list):
            props["env_count"] = len(env)
        elif isinstance(env, dict):
            props["env_count"] = len(env)

        build = svc_config.get("build")
        if build:
            props["build"] = str(build) if isinstance(build, str) else "custom"
            if not image:
                node_type = NodeType.CONTAINER

        node_id = f"service/{svc_name}"
        service_ids.append(node_id)

        node = InfraNode(
            id=node_id,
            name=svc_name,
            node_type=node_type,
            provider="docker",
            source_file=str(compose_file),
            properties=props,
        )
        diagram.add_node(node)

        # Edges from depends_on
        depends = svc_config.get("depends_on", [])
        if isinstance(depends, dict):
            depends = list(depends.keys())
        for dep in depends:
            diagram.add_edge(InfraEdge(
                source=node_id,
                target=f"service/{dep}",
                label="depends on",
            ))

        # Edges from links
        links = svc_config.get("links", [])
        for link in links:
            link_name = link.split(":")[0]
            diagram.add_edge(InfraEdge(
                source=node_id,
                target=f"service/{link_name}",
                label="links",
            ))

        # Edges to volumes
        svc_volumes = svc_config.get("volumes", [])
        for v in svc_volumes:
            if isinstance(v, str) and ":" in v:
                vol_name = v.split(":")[0]
                if vol_name in volumes:
                    diagram.add_edge(InfraEdge(
                        source=node_id,
                        target=f"volume/{vol_name}",
                        label="mounts",
                    ))

        # Edges to networks
        svc_networks = svc_config.get("networks", [])
        if isinstance(svc_networks, list):
            for net in svc_networks:
                diagram.add_edge(InfraEdge(
                    source=node_id,
                    target=f"network/{net}",
                    label="in",
                ))
        elif isinstance(svc_networks, dict):
            for net in svc_networks:
                diagram.add_edge(InfraEdge(
                    source=node_id,
                    target=f"network/{net}",
                    label="in",
                ))

    # Group all services
    if service_ids:
        diagram.add_group(InfraGroup(
            id="services",
            name="Docker Services",
            node_ids=service_ids,
        ))


def _infer_type_from_image(image: str) -> NodeType:
    """Infer node type from Docker image name."""
    image_lower = image.lower().split(":")[0].split("/")[-1]
    for key, node_type in _IMAGE_TYPE_MAP.items():
        if key in image_lower:
            return node_type
    return NodeType.CONTAINER
