"""K8s resource dict builders.

Every K8s resource dict is built here, NOT inline in SDK services.
This centralizes all K8s resource construction for easy auditing,
testing, and future --dry-run support.
"""

from __future__ import annotations

import hashlib
from datetime import UTC, datetime
from typing import Any

from kuberoku.k8s import labels


def safe_name(prefix: str, app: str, suffix: str = "", max_length: int = 63) -> str:
    """Build a K8s resource name, truncating only the app portion if needed.

    K8s resource names are limited to 63 characters (RFC 1123 DNS label).
    If the full name exceeds this, only the app portion is truncated and a
    stable 4-char hash suffix is appended for uniqueness.
    """
    if suffix:
        full = f"{prefix}-{app}-{suffix}"
        shell = f"{prefix}--{suffix}"
    else:
        full = f"{prefix}-{app}"
        shell = f"{prefix}-"

    if len(full) <= max_length:
        return full

    budget = max_length - len(shell) - 5  # 5 = "-" + 4-char hash
    hash4 = hashlib.sha256(app.encode()).hexdigest()[:4]
    app_trunc = app[:budget]
    if suffix:
        return f"{prefix}-{app_trunc}-{hash4}-{suffix}"
    return f"{prefix}-{app_trunc}-{hash4}"


def _utcnow_iso() -> str:
    """Current UTC time in ISO 8601 format."""
    return datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")


def build_app_manifest(prefix: str, domain: str, app: str, created_by: str) -> dict[str, Any]:
    """Build the app manifest ConfigMap dict."""
    return {
        "apiVersion": "v1",
        "kind": "ConfigMap",
        "metadata": {
            "name": safe_name(prefix, "app", app),
            "labels": labels.for_app(domain, prefix, app, "app-manifest"),
            "annotations": {
                f"{domain}/created-at": _utcnow_iso(),
                f"{domain}/created-by": created_by,
            },
        },
        "data": {
            "current_release_version": "0",
        },
    }


def build_env_configmap(prefix: str, domain: str, app: str) -> dict[str, Any]:
    """Build the environment ConfigMap dict (initially empty)."""
    return {
        "apiVersion": "v1",
        "kind": "ConfigMap",
        "metadata": {
            "name": safe_name(prefix, "env", app),
            "labels": labels.for_app(domain, prefix, app, "env"),
        },
        "data": {},
    }


def build_formation_configmap(prefix: str, domain: str, app: str) -> dict[str, Any]:
    """Build the formation ConfigMap dict (initially empty)."""
    return {
        "apiVersion": "v1",
        "kind": "ConfigMap",
        "metadata": {
            "name": safe_name(prefix, "formation", app),
            "labels": labels.for_app(domain, prefix, app, "formation"),
        },
        "data": {},
    }


def build_secret(prefix: str, domain: str, app: str) -> dict[str, Any]:
    """Build the Secret dict for sensitive config vars."""
    return {
        "apiVersion": "v1",
        "kind": "Secret",
        "metadata": {
            "name": safe_name(prefix, "secret", app),
            "labels": labels.for_app(domain, prefix, app, "secret"),
        },
        "type": "Opaque",
        "stringData": {},
    }


def build_deployment(
    prefix: str,
    domain: str,
    app: str,
    process: str,
    image: str,
    replicas: int,
    command: str | None,
    ports: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    """Build a Deployment dict for a process type."""
    container: dict[str, Any] = {
        "name": process,
        "image": image,
        "envFrom": [
            {"configMapRef": {"name": safe_name(prefix, "env", app)}},
            {
                "secretRef": {
                    "name": safe_name(prefix, "secret", app),
                    "optional": True,
                }
            },
        ],
    }
    if command:
        container["command"] = ["/bin/sh", "-c", command]
    if ports:
        container["ports"] = ports
    return {
        "apiVersion": "apps/v1",
        "kind": "Deployment",
        "metadata": {
            "name": safe_name(prefix, app, process),
            "labels": labels.for_process(domain, prefix, app, process),
        },
        "spec": {
            "replicas": replicas,
            "selector": {
                "matchLabels": labels.selector(domain, app, process),
            },
            "template": {
                "metadata": {
                    "labels": labels.for_process(domain, prefix, app, process),
                },
                "spec": {
                    "terminationGracePeriodSeconds": 5,
                    "containers": [container],
                },
            },
        },
    }


def build_service(
    prefix: str,
    domain: str,
    app: str,
    process: str,
    ports: list[dict[str, Any]],
    service_type: str = "ClusterIP",
) -> dict[str, Any]:
    """Build a Service dict for a process type."""
    return {
        "apiVersion": "v1",
        "kind": "Service",
        "metadata": {
            "name": safe_name(prefix, app, process),
            "labels": labels.for_process(domain, prefix, app, process),
        },
        "spec": {
            "type": service_type,
            "selector": labels.selector(domain, app, process),
            "ports": ports,
        },
    }


def build_ingress_rule(
    hostname: str,
    service_name: str,
    service_port: int,
) -> dict[str, Any]:
    """Build a single Ingress rule dict."""
    return {
        "host": hostname,
        "http": {
            "paths": [
                {
                    "path": "/",
                    "pathType": "Prefix",
                    "backend": {
                        "service": {
                            "name": service_name,
                            "port": {"number": service_port},
                        }
                    },
                }
            ]
        },
    }


def build_tls_entry(hostname: str, secret_name: str) -> dict[str, Any]:
    """Build a single TLS entry for an Ingress."""
    return {
        "hosts": [hostname],
        "secretName": secret_name,
    }


def build_ingress(
    prefix: str,
    domain: str,
    app: str,
    rules: list[dict[str, Any]],
    tls: list[dict[str, Any]] | None = None,
    ingress_class: str | None = None,
    issuer: str | None = None,
    auto_hostnames: list[str] | None = None,
) -> dict[str, Any]:
    """Build an Ingress dict for an app with multiple rules.

    One Ingress per app. Multiple domains = multiple rules.
    TLS via cert-manager ClusterIssuer annotations.
    """
    annotations: dict[str, str] = {}
    if issuer:
        annotations["cert-manager.io/cluster-issuer"] = issuer
    if auto_hostnames:
        annotations[f"{domain}/auto-domains"] = ",".join(auto_hostnames)

    metadata: dict[str, Any] = {
        "name": safe_name(prefix, "ingress", app),
        "labels": labels.for_app(domain, prefix, app, "ingress"),
    }
    if annotations:
        metadata["annotations"] = annotations

    spec: dict[str, Any] = {"rules": rules}
    if ingress_class:
        spec["ingressClassName"] = ingress_class
    if tls:
        spec["tls"] = tls

    return {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "Ingress",
        "metadata": metadata,
        "spec": spec,
    }


def build_statefulset(
    prefix: str,
    domain: str,
    app: str,
    instance: str,
    addon_type: str,
    image: str,
    port: int,
    command: list[str] | None = None,
    storage_size: str = "1Gi",
    storage_class: str | None = None,
    volumes: dict[str, str] | None = None,
    container_env: list[dict[str, Any]] | None = None,
    resources_spec: dict[str, Any] | None = None,
    readiness_probe: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Build a StatefulSet dict for an addon."""
    lbl = labels.for_addon(domain, prefix, app, instance, addon_type)
    name = safe_name(prefix, "addon", f"{app}-{instance}")
    container: dict[str, Any] = {
        "name": instance,
        "image": image,
        "ports": [{"containerPort": port}],
    }
    if command:
        container["command"] = command
    if container_env:
        container["env"] = container_env
    if resources_spec:
        container["resources"] = resources_spec
    if readiness_probe:
        container["readinessProbe"] = readiness_probe
    volume_mounts: list[dict[str, str]] = []
    volume_claims: list[dict[str, Any]] = []
    if volumes:
        for mount_path, vol_name in volumes.items():
            volume_mounts.append({"name": vol_name, "mountPath": mount_path})
            pvc_spec: dict[str, Any] = {
                "accessModes": ["ReadWriteOnce"],
                "resources": {"requests": {"storage": storage_size}},
            }
            if storage_class:
                pvc_spec["storageClassName"] = storage_class
            volume_claims.append(
                {
                    "metadata": {
                        "name": vol_name,
                        "labels": lbl,
                    },
                    "spec": pvc_spec,
                }
            )
    if volume_mounts:
        container["volumeMounts"] = volume_mounts
    result: dict[str, Any] = {
        "apiVersion": "apps/v1",
        "kind": "StatefulSet",
        "metadata": {"name": name, "labels": lbl},
        "spec": {
            "replicas": 1,
            "serviceName": name,
            "selector": {
                "matchLabels": {
                    f"{domain}/app": app,
                    f"{domain}/addon-instance": instance,
                },
            },
            "template": {
                "metadata": {"labels": lbl},
                "spec": {
                    "terminationGracePeriodSeconds": 5,
                    "containers": [container],
                },
            },
        },
    }
    if volume_claims:
        result["spec"]["volumeClaimTemplates"] = volume_claims
    return result


def build_addon_metadata_configmap(
    prefix: str,
    domain: str,
    app: str,
    instance: str,
    addon_type: str,
    data: dict[str, str],
) -> dict[str, Any]:
    """Build the addon metadata ConfigMap dict.

    Stores addon state as key-value pairs (type, status, image, cpu, etc.).
    """
    lbl = labels.for_addon(domain, prefix, app, instance, addon_type)
    return {
        "apiVersion": "v1",
        "kind": "ConfigMap",
        "metadata": {
            "name": safe_name(prefix, "addon-meta", f"{app}-{instance}"),
            "labels": lbl,
        },
        "data": data,
    }


def build_addon_credential_secret(
    prefix: str,
    domain: str,
    app: str,
    instance: str,
    addon_type: str,
    credentials: dict[str, str],
) -> dict[str, Any]:
    """Build the addon credential Secret dict."""
    lbl = labels.for_addon(domain, prefix, app, instance, addon_type)
    return {
        "apiVersion": "v1",
        "kind": "Secret",
        "metadata": {
            "name": safe_name(prefix, "addon-secret", f"{app}-{instance}"),
            "labels": lbl,
        },
        "type": "Opaque",
        "stringData": credentials,
    }


def addon_pvc_name(prefix: str, app: str, instance: str, volume_name: str) -> str:
    """Compute the deterministic PVC name for a StatefulSet addon.

    K8s names PVCs as: {volumeClaimTemplate.name}-{statefulset.name}-{ordinal}
    Since addons always have 1 replica, ordinal is always 0.
    """
    sts_name = safe_name(prefix, "addon", f"{app}-{instance}")
    return f"{volume_name}-{sts_name}-0"


def build_release_configmap(
    prefix: str,
    domain: str,
    app: str,
    version: int,
    data: dict[str, str],
) -> dict[str, Any]:
    """Build a release ConfigMap dict (one per release)."""
    return {
        "apiVersion": "v1",
        "kind": "ConfigMap",
        "metadata": {
            "name": safe_name(prefix, "release", f"{app}-v{version}"),
            "labels": labels.for_app(domain, prefix, app, "release"),
        },
        "data": data,
    }


def build_app_deny_policy(prefix: str, domain: str, app: str) -> dict[str, Any]:
    """Build the default-deny NetworkPolicy for an app.

    Selects all pods with the app label and denies all ingress.
    This is the foundation of app isolation — everything starts locked down.
    """
    return {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "NetworkPolicy",
        "metadata": {
            "name": safe_name(prefix, "netpol-deny", app),
            "labels": labels.for_app(domain, prefix, app, "network-policy-deny"),
        },
        "spec": {
            "podSelector": {
                "matchLabels": {
                    f"{domain}/app": app,
                },
            },
            "policyTypes": ["Ingress"],
            "ingress": [],
        },
    }


def build_process_allow_policy(
    prefix: str,
    domain: str,
    app: str,
    process: str,
    ports: list[dict[str, Any]],
) -> dict[str, Any]:
    """Build a NetworkPolicy allowing same-app ingress on declared ports for a process."""
    return {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "NetworkPolicy",
        "metadata": {
            "name": safe_name(prefix, "netpol-allow", f"{app}-{process}"),
            "labels": {
                **labels.for_app(domain, prefix, app, "network-policy-allow"),
                f"{domain}/process-type": process,
            },
        },
        "spec": {
            "podSelector": {
                "matchLabels": {
                    f"{domain}/app": app,
                    f"{domain}/process-type": process,
                },
            },
            "policyTypes": ["Ingress"],
            "ingress": [
                {
                    "from": [
                        {
                            "podSelector": {
                                "matchLabels": {
                                    f"{domain}/app": app,
                                },
                            },
                        },
                    ],
                    "ports": ports,
                },
            ],
        },
    }


def build_addon_allow_policy(
    prefix: str,
    domain: str,
    app: str,
    instance: str,
    addon_type: str,
    port: int,
) -> dict[str, Any]:
    """Build a NetworkPolicy allowing same-app ingress on the addon port."""
    return {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "NetworkPolicy",
        "metadata": {
            "name": safe_name(prefix, "netpol-allow", f"{app}-{instance}"),
            "labels": {
                **labels.for_app(domain, prefix, app, "network-policy-allow"),
                f"{domain}/addon-instance": instance,
                f"{domain}/addon-type": addon_type,
            },
        },
        "spec": {
            "podSelector": {
                "matchLabels": {
                    f"{domain}/app": app,
                    f"{domain}/addon-instance": instance,
                },
            },
            "policyTypes": ["Ingress"],
            "ingress": [
                {
                    "from": [
                        {
                            "podSelector": {
                                "matchLabels": {
                                    f"{domain}/app": app,
                                },
                            },
                        },
                    ],
                    "ports": [{"port": port, "protocol": "TCP"}],
                },
            ],
        },
    }


def build_external_allow_policy(
    prefix: str,
    domain: str,
    app: str,
    process: str,
    ports: list[dict[str, Any]],
    exposed_by: str = "expose",
) -> dict[str, Any]:
    """Build a NetworkPolicy allowing external ingress on declared ports.

    No `from` field = allow from ALL sources (external, ingress controllers, other pods).
    Created on expose / domains:add. Deleted on unexpose / domains:remove.
    The `exposed-by` annotation tracks who created the policy (expose, domains).
    """
    return {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "NetworkPolicy",
        "metadata": {
            "name": safe_name(prefix, "netpol-external", f"{app}-{process}"),
            "labels": {
                **labels.for_app(domain, prefix, app, "network-policy-external"),
                f"{domain}/process-type": process,
            },
            "annotations": {
                f"{domain}/exposed-by": exposed_by,
            },
        },
        "spec": {
            "podSelector": {
                "matchLabels": {
                    f"{domain}/app": app,
                    f"{domain}/process-type": process,
                },
            },
            "policyTypes": ["Ingress"],
            "ingress": [
                {
                    "ports": ports,
                },
            ],
        },
    }


def build_addon_external_allow_policy(
    prefix: str,
    domain: str,
    app: str,
    instance: str,
    addon_type: str,
    ports: list[dict[str, Any]],
    exposed_by: str = "expose",
) -> dict[str, Any]:
    """Build a NetworkPolicy allowing external ingress on addon ports.

    Same structure as build_external_allow_policy but uses addon-instance
    label for pod selection instead of process-type.
    """
    return {
        "apiVersion": "networking.k8s.io/v1",
        "kind": "NetworkPolicy",
        "metadata": {
            "name": safe_name(prefix, "netpol-external", f"{app}-{instance}"),
            "labels": {
                **labels.for_app(domain, prefix, app, "network-policy-external"),
                f"{domain}/addon-instance": instance,
                f"{domain}/addon-type": addon_type,
            },
            "annotations": {
                f"{domain}/exposed-by": exposed_by,
            },
        },
        "spec": {
            "podSelector": {
                "matchLabels": {
                    f"{domain}/app": app,
                    f"{domain}/addon-instance": instance,
                },
            },
            "policyTypes": ["Ingress"],
            "ingress": [
                {
                    "ports": ports,
                },
            ],
        },
    }


def build_job(
    prefix: str,
    domain: str,
    app: str,
    run_id: str,
    image: str,
    command: list[str],
    extra_env: list[dict[str, str]] | None = None,
    timeout: int = 3600,
) -> dict[str, Any]:
    """Build a Job dict for a one-off run command."""
    container: dict[str, Any] = {
        "name": "run",
        "image": image,
        "command": command,
        "envFrom": [
            {
                "configMapRef": {
                    "name": safe_name(prefix, "env", app),
                }
            },
            {
                "secretRef": {
                    "name": safe_name(prefix, "secret", app),
                    "optional": True,
                }
            },
        ],
    }
    if extra_env:
        container["env"] = extra_env
    return {
        "apiVersion": "batch/v1",
        "kind": "Job",
        "metadata": {
            "name": safe_name(prefix, "run", f"{app}-{run_id}"),
            "labels": labels.for_app(domain, prefix, app, "run"),
        },
        "spec": {
            "activeDeadlineSeconds": timeout,
            "template": {
                "metadata": {
                    "labels": labels.for_app(domain, prefix, app, "run"),
                },
                "spec": {
                    "containers": [container],
                    "restartPolicy": "Never",
                },
            },
            "backoffLimit": 0,
        },
    }
