# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Compose file generator — produces ``familiar-compose.yml`` from service specs.

Convenience for users who prefer managing services via ``podman-compose``
or ``docker compose`` instead of the dashboard UI.
"""

import os
from pathlib import Path
from typing import Dict, List, Optional

import yaml

from familiar.services.specs import SERVICE_SPECS, ServiceType


def generate_compose(
    service_keys: Optional[List[str]] = None,
    runtime: str = "podman",
) -> str:
    """Generate a Compose YAML string for the requested services.

    Args:
        service_keys: List of service keys to include. ``None`` includes all
            container services.
        runtime: ``"podman"`` or ``"docker"`` — only affects the header comment.

    Returns:
        YAML string suitable for writing to ``familiar-compose.yml``.
    """
    if service_keys is None:
        service_keys = [
            k for k, s in SERVICE_SPECS.items() if s.service_type == ServiceType.CONTAINER
        ]

    home = str(Path.home())
    services: Dict[str, dict] = {}

    # Check if any services form a stack (have depends_on)
    needs_network = any(SERVICE_SPECS.get(k) and SERVICE_SPECS[k].depends_on for k in service_keys)

    for key in service_keys:
        spec = SERVICE_SPECS.get(key)
        if not spec or spec.service_type != ServiceType.CONTAINER:
            continue

        svc: dict = {
            "image": spec.image,
            "container_name": spec.default_container_name,
            "restart": "unless-stopped",
        }

        if spec.ports:
            if spec.localhost_only:
                svc["ports"] = [
                    f"127.0.0.1:{host}:{container}" for host, container in spec.ports.items()
                ]
            else:
                svc["ports"] = [f"{host}:{container}" for host, container in spec.ports.items()]

        if spec.volumes:
            resolved = spec.get_resolved_volumes(home)
            svc["volumes"] = [f"{hp}:{cp}" for hp, cp in resolved.items()]

        if spec.env_vars:
            svc["environment"] = dict(spec.env_vars)

        # Security hardening (NIST SP 800-190)
        # Services that disable no_new_privileges need default caps (no drop)
        if spec.no_new_privileges:
            svc["cap_drop"] = ["ALL"]
            if spec.capabilities:
                svc["cap_add"] = list(spec.capabilities)
            svc["security_opt"] = ["no-new-privileges:true"]
        else:
            # Relaxed security — keep default caps, only add extras
            if spec.capabilities:
                svc["cap_add"] = list(spec.capabilities)

        if spec.read_only_root:
            svc["read_only"] = True

        if spec.tmpfs_mounts:
            svc["tmpfs"] = [f"{m}:rw,noexec,nosuid" for m in spec.tmpfs_mounts]

        deploy: dict = {}
        if spec.memory_limit:
            deploy.setdefault("resources", {}).setdefault("limits", {})["memory"] = (
                spec.memory_limit
            )
        if spec.cpu_limit and spec.cpu_limit != 1.0:
            deploy.setdefault("resources", {}).setdefault("limits", {})["cpus"] = str(
                spec.cpu_limit
            )
        if deploy:
            svc["deploy"] = deploy

        if spec.depends_on:
            svc["depends_on"] = [d for d in spec.depends_on if d in service_keys]

        # Stack services need a shared network for inter-container DNS
        if needs_network:
            svc["networks"] = ["familiar"]

        services[key] = svc

    compose: dict = {
        "services": services,
    }

    # Add shared network if any services use depends_on
    if needs_network:
        compose["networks"] = {
            "familiar": {"driver": "bridge"},
        }

    header = (
        f"# Familiar self-hosted services — generated for {runtime}\n"
        f"# Re-generate via: familiar-server compose\n"
        f"# Run with: {runtime}-compose -f familiar-compose.yml up -d\n"
    )

    return header + yaml.dump(compose, default_flow_style=False, sort_keys=False)


def write_compose_file(
    output_path: Optional[str] = None,
    service_keys: Optional[List[str]] = None,
    runtime: str = "podman",
) -> str:
    """Write a ``familiar-compose.yml`` file to disk.

    Args:
        output_path: Destination path. Defaults to ``~/.familiar/services/familiar-compose.yml``.
        service_keys: Service keys to include (``None`` = all container services).
        runtime: ``"podman"`` or ``"docker"``.

    Returns:
        The absolute path to the written file.
    """
    if output_path is None:
        output_path = os.path.join(
            str(Path.home()), ".familiar", "services", "familiar-compose.yml"
        )

    content = generate_compose(service_keys=service_keys, runtime=runtime)
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)

    with open(output_path, "w") as f:
        f.write(content)

    return os.path.abspath(output_path)
