"""Bidirectional conversion between devcontainer and gaol formats.

This module provides converters to translate between VS Code's devcontainer.json
format and gaol's ContainerSpec format.
"""

from __future__ import annotations

import os
import re
from pathlib import Path
from typing import Any

from gaol.devcontainer.models import (
    DevcontainerBuild,
    DevcontainerCustomizations,
    DevcontainerMount,
    DevcontainerSpec,
    DevcontainerVSCodeCustomizations,
)
from gaol.specs.models import ContainerSpec, SpecNetwork, SpecResources


# Profile to VS Code extension mapping
PROFILE_EXTENSIONS: dict[str, list[str]] = {
    "dev-python": ["ms-python.python", "ms-python.pylint"],
    "dev-node": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"],
    "dev-rust": ["rust-lang.rust-analyzer"],
    "dev-go": ["golang.go"],
    "dev-java": ["vscjava.vscode-java-pack"],
    "dev-cpp": ["ms-vscode.cpptools"],
    "dev-dotnet": ["ms-dotnettools.csharp"],
}


class DevcontainerConverter:
    """Convert between devcontainer and gaol formats.

    This converter handles the translation between VS Code's devcontainer.json
    specification and gaol's ContainerSpec format, preserving as much
    information as possible for round-trip conversions.
    """

    def __init__(self):
        """Initialize converter."""
        pass

    def to_container_spec(
        self,
        devcontainer: DevcontainerSpec,
        name: str | None = None,
        runtime: str = "docker",
        distro: str = "trixie",
    ) -> ContainerSpec:
        """Convert DevcontainerSpec to gaol ContainerSpec.

        Args:
            devcontainer: DevcontainerSpec to convert.
            name: Override container name. Uses devcontainer name if not provided.
            runtime: Runtime to use (default: docker).
            distro: Base distro (default: trixie).

        Returns:
            Converted ContainerSpec.
        """
        # Determine name
        container_name = name or devcontainer.name
        if not container_name:
            # Generate name from image
            if devcontainer.image:
                container_name = self._image_to_name(devcontainer.image)
            else:
                container_name = "dev-container"

        # Sanitize name
        container_name = self._sanitize_name(container_name)

        # Convert ports
        ports = [f"{p}:{p}" for p in devcontainer.forward_ports]

        # Convert mounts to volumes
        volumes = self._convert_mounts_to_volumes(devcontainer.mounts)

        # Merge environment variables
        environment = dict(devcontainer.container_env)
        environment.update(devcontainer.remote_env)

        # Determine working directory
        working_dir = devcontainer.workspace_folder

        # Build spec
        spec = ContainerSpec(
            name=container_name,
            runtime=runtime,
            distro=distro,
            image=devcontainer.image,
            profiles=[],
            resources=SpecResources(),
            network=SpecNetwork(ports=ports),
            volumes=volumes,
            environment=environment,
            working_dir=working_dir,
            privileged=devcontainer.privileged,
            capabilities_add=list(devcontainer.cap_add),
        )

        return spec

    def from_container_spec(
        self,
        spec: ContainerSpec,
        vscode_extensions: list[str] | None = None,
        post_create_command: str | list[str] | None = None,
        remote_user: str | None = None,
    ) -> DevcontainerSpec:
        """Convert gaol ContainerSpec to DevcontainerSpec.

        Args:
            spec: ContainerSpec to convert.
            vscode_extensions: Additional VS Code extensions to include.
            post_create_command: Command to run after container creation.
            remote_user: Remote user for VS Code.

        Returns:
            Converted DevcontainerSpec.
        """
        # Convert ports
        forward_ports = self._extract_forward_ports(spec.network.ports)

        # Convert volumes to mounts
        mounts = self._convert_volumes_to_mounts(spec.volumes)

        # Build extensions list from profiles
        extensions: list[str] = []
        for profile in spec.profiles:
            if profile in PROFILE_EXTENSIONS:
                extensions.extend(PROFILE_EXTENSIONS[profile])
        if vscode_extensions:
            extensions.extend(vscode_extensions)
        # Deduplicate while preserving order
        extensions = list(dict.fromkeys(extensions))

        # Build customizations
        customizations = DevcontainerCustomizations(
            vscode=DevcontainerVSCodeCustomizations(extensions=extensions)
        )

        return DevcontainerSpec(
            name=spec.name,
            image=spec.image,
            forward_ports=forward_ports,
            mounts=mounts,
            container_env=spec.environment,
            workspace_folder=spec.working_dir or "/workspace",
            post_create_command=post_create_command,
            customizations=customizations,
            privileged=spec.privileged,
            cap_add=spec.capabilities_add,
            remote_user=remote_user,
        )

    def _image_to_name(self, image: str) -> str:
        """Extract a suitable name from an image name.

        Examples:
            python:3.11 -> python
            mcr.microsoft.com/devcontainers/python:3.11 -> python
            my-app:latest -> my-app
        """
        # Remove registry prefix
        if "/" in image:
            image = image.rsplit("/", 1)[-1]

        # Remove tag
        if ":" in image:
            image = image.split(":")[0]

        return image

    def _sanitize_name(self, name: str) -> str:
        """Sanitize a name to be a valid container name.

        Container names must:
        - Start with alphanumeric
        - Contain only alphanumeric, underscore, period, hyphen
        """
        # Replace spaces and other invalid chars with hyphen
        name = re.sub(r"[^a-zA-Z0-9_.-]", "-", name.lower())

        # Ensure starts with alphanumeric
        if name and not name[0].isalnum():
            name = "c" + name

        # Limit length
        if len(name) > 64:
            name = name[:64]

        return name or "container"

    def _convert_mounts_to_volumes(
        self, mounts: list[str | DevcontainerMount]
    ) -> list[str]:
        """Convert devcontainer mounts to gaol volume format."""
        volumes: list[str] = []

        for mount in mounts:
            if isinstance(mount, str):
                # Parse mount string
                mount = DevcontainerMount.from_string(mount)

            # Expand environment variables in source
            source = self._expand_devcontainer_vars(mount.source)
            target = mount.target

            if mount.readonly:
                volumes.append(f"{source}:{target}:ro")
            else:
                volumes.append(f"{source}:{target}")

        return volumes

    def _convert_volumes_to_mounts(self, volumes: list[str | dict]) -> list[str]:
        """Convert gaol volumes to devcontainer mount format."""
        mounts: list[str] = []

        for vol in volumes:
            if isinstance(vol, dict):
                source = vol.get("host") or vol.get("source", "")
                target = vol.get("container") or vol.get("target", "")
                readonly = vol.get("readonly", False) or vol.get("ro", False)
            else:
                # Parse string format
                parts = vol.split(":")
                if len(parts) >= 2:
                    source = parts[0]
                    target = parts[1]
                    readonly = len(parts) > 2 and parts[2].lower() == "ro"
                else:
                    continue

            # Convert to devcontainer mount format
            mount_parts = [f"type=bind", f"source={source}", f"target={target}"]
            if readonly:
                mount_parts.append("readonly")

            mounts.append(",".join(mount_parts))

        return mounts

    def _extract_forward_ports(self, ports: list[str | dict]) -> list[int]:
        """Extract forward port numbers from port mappings."""
        forward_ports: list[int] = []

        for port in ports:
            if isinstance(port, dict):
                # Use host port or container port
                p = port.get("host") or port.get("container")
                if p:
                    forward_ports.append(int(p))
            elif isinstance(port, str):
                # Parse "host:container" or just "port"
                if ":" in port:
                    # Extract host port
                    parts = port.split(":")
                    # Handle formats like "8080:80" or "0.0.0.0:8080:80"
                    if len(parts) == 2:
                        forward_ports.append(int(parts[0].split("/")[0]))
                    elif len(parts) == 3:
                        forward_ports.append(int(parts[1].split("/")[0]))
                else:
                    forward_ports.append(int(port.split("/")[0]))

        return forward_ports

    def _expand_devcontainer_vars(self, value: str) -> str:
        """Expand devcontainer variable syntax.

        Converts ${localEnv:VAR} to actual path.
        """
        # Pattern: ${localEnv:VAR} or ${localEnv:VAR:default}
        pattern = r"\$\{localEnv:([^}:]+)(?::([^}]*))?\}"

        def replace(match: re.Match) -> str:
            var_name = match.group(1)
            default = match.group(2) or ""
            return os.environ.get(var_name, default)

        return re.sub(pattern, replace, value)


# Convenience functions
def devcontainer_to_spec(
    devcontainer: DevcontainerSpec,
    name: str | None = None,
    runtime: str = "docker",
) -> ContainerSpec:
    """Convert DevcontainerSpec to gaol ContainerSpec.

    Args:
        devcontainer: DevcontainerSpec to convert.
        name: Override container name.
        runtime: Runtime to use.

    Returns:
        Converted ContainerSpec.
    """
    converter = DevcontainerConverter()
    return converter.to_container_spec(devcontainer, name=name, runtime=runtime)


def spec_to_devcontainer(
    spec: ContainerSpec,
    vscode_extensions: list[str] | None = None,
    post_create_command: str | list[str] | None = None,
) -> DevcontainerSpec:
    """Convert gaol ContainerSpec to DevcontainerSpec.

    Args:
        spec: ContainerSpec to convert.
        vscode_extensions: Additional VS Code extensions.
        post_create_command: Command to run after creation.

    Returns:
        Converted DevcontainerSpec.
    """
    converter = DevcontainerConverter()
    return converter.from_container_spec(
        spec,
        vscode_extensions=vscode_extensions,
        post_create_command=post_create_command,
    )
