"""Host-side CLI helper for devcontainer.json integration with dx.

Invoked by dx via: uv run python -m gaol.devcontainer.cli_helper

Subcommands:
    overlays <project_dir>  - Print space-separated overlay names to stdout
    parse <project_dir>     - Print JSON blob (env, lifecycle, features, ports)
"""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any

from gaol.devcontainer.loader import DevcontainerLoader
from gaol.devcontainer.models import DevcontainerSpec

# Image name fragment -> overlay mapping
IMAGE_OVERLAY_MAP: dict[str, str] = {
    "python": "python",
    "devcontainers/python": "python",
    "node": "node",
    "javascript-node": "node",
    "devcontainers/javascript-node": "node",
    "typescript-node": "node",
    "devcontainers/typescript-node": "node",
    "rust": "rust",
    "devcontainers/rust": "rust",
    "golang": "go",
    "go": "go",
    "devcontainers/go": "go",
}

# Feature URI fragment -> overlay mapping
FEATURE_OVERLAY_MAP: dict[str, str] = {
    "devcontainers/features/python": "python",
    "devcontainers/features/node": "node",
    "devcontainers/features/rust": "rust",
    "devcontainers/features/go": "go",
}


def image_to_overlays(image: str | None) -> list[str]:
    """Map a container image name to overlay names.

    Checks the image string against known patterns. For registry-qualified
    images like mcr.microsoft.com/devcontainers/python:3.11, we strip the
    registry prefix and tag, then match against known fragments.

    Args:
        image: Docker image name (e.g. "mcr.microsoft.com/devcontainers/python:3.11")

    Returns:
        List of overlay names (e.g. ["python"])
    """
    if not image:
        return []

    overlays: list[str] = []

    # Strip tag
    image_no_tag = image.split(":")[0]

    # Try matching against known patterns (longest match first)
    for pattern, overlay in sorted(IMAGE_OVERLAY_MAP.items(), key=lambda x: -len(x[0])):
        if pattern in image_no_tag and overlay not in overlays:
            overlays.append(overlay)

    return overlays


def features_to_overlays(features: dict[str, Any]) -> list[str]:
    """Map devcontainer features to overlay names.

    Args:
        features: Dict of feature URI -> config from devcontainer.json

    Returns:
        List of overlay names
    """
    overlays: list[str] = []

    for feature_uri in features:
        for pattern, overlay in FEATURE_OVERLAY_MAP.items():
            if pattern in feature_uri and overlay not in overlays:
                overlays.append(overlay)

    return overlays


def flatten_lifecycle_command(
    cmd: str | list[str] | dict[str, str | list[str]] | None,
) -> str | None:
    """Flatten a devcontainer lifecycle command to a single shell string.

    Devcontainer lifecycle commands can be:
    - string: "npm install"
    - list: ["npm", "install"]
    - dict: {"install": "npm install", "build": "npm run build"}

    Args:
        cmd: Lifecycle command in any supported format

    Returns:
        Shell command string, or None if empty
    """
    if cmd is None:
        return None

    if isinstance(cmd, str):
        return cmd if cmd.strip() else None

    if isinstance(cmd, list):
        if not cmd:
            return None
        # Join as a single command line (space-separated args)
        return " ".join(cmd)

    if isinstance(cmd, dict):
        if not cmd:
            return None
        # Run each named command sequentially
        parts = []
        for value in cmd.values():
            if isinstance(value, list):
                parts.append(" ".join(value))
            elif isinstance(value, str) and value.strip():
                parts.append(value)
        return " && ".join(parts) if parts else None

    return None


def get_overlays(spec: DevcontainerSpec) -> list[str]:
    """Get all overlay names for a devcontainer spec.

    Combines image-based and feature-based overlay detection.
    """
    overlays: list[str] = []
    overlays.extend(image_to_overlays(spec.image))
    overlays.extend(o for o in features_to_overlays(spec.features) if o not in overlays)
    return overlays


def get_parsed_state(spec: DevcontainerSpec) -> dict[str, Any]:
    """Build the JSON state blob for the devcontainer overlay.

    Returns a dict with:
    - env: merged environment variables
    - onCreateCommand: flattened string or null
    - postCreateCommand: flattened string or null
    - postStartCommand: flattened string or null
    - features: original features dict
    - forwardPorts: list of ports
    - remoteUser: remote user or null
    - workspaceFolder: workspace folder or null
    """
    return {
        "env": spec.get_all_environment(),
        "onCreateCommand": flatten_lifecycle_command(spec.on_create_command),
        "postCreateCommand": flatten_lifecycle_command(spec.post_create_command),
        "postStartCommand": flatten_lifecycle_command(spec.post_start_command),
        "features": spec.features,
        "forwardPorts": spec.forward_ports,
        "remoteUser": spec.remote_user,
        "workspaceFolder": spec.workspace_folder,
    }


def cmd_overlays(project_dir: str) -> None:
    """Print space-separated overlay names to stdout."""
    loader = DevcontainerLoader(Path(project_dir))
    path = loader.find_devcontainer()
    if path is None:
        return

    spec = loader.load(path)
    overlays = get_overlays(spec)
    if overlays:
        print(" ".join(overlays))


def cmd_parse(project_dir: str) -> None:
    """Print full JSON state blob to stdout."""
    loader = DevcontainerLoader(Path(project_dir))
    path = loader.find_devcontainer()
    if path is None:
        print("{}", file=sys.stdout)
        return

    spec = loader.load(path)
    state = get_parsed_state(spec)
    print(json.dumps(state, indent=2))


def main() -> None:
    """CLI entry point."""
    if len(sys.argv) < 3:
        print(f"Usage: {sys.argv[0]} <overlays|parse> <project_dir>", file=sys.stderr)
        sys.exit(1)

    command = sys.argv[1]
    project_dir = sys.argv[2]

    if command == "overlays":
        cmd_overlays(project_dir)
    elif command == "parse":
        cmd_parse(project_dir)
    else:
        print(f"Unknown command: {command}", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()
