"""Shared infrastructure helpers for the infra command group."""

import base64
import json
import subprocess
import tempfile
from pathlib import Path
from uuid import UUID

from wasabi import msg

from prodigy_teams_pam_sdk import Client
from prodigy_teams_pam_sdk.models import BrokerCredentials


def get_cluster_credentials(project: str, zone: str, cluster_name: str) -> None:
    """Run gcloud container clusters get-credentials to configure kubectl."""
    subprocess.run(
        [
            "gcloud",
            "container",
            "clusters",
            "get-credentials",
            cluster_name,
            "--zone",
            zone,
            "--project",
            project,
        ],
        check=True,
        text=True,
    )


def ensure_broker_keypair(
    infra_secret_name: str, namespace: str = "prodigy-teams"
) -> str:
    """Ensure the infra K8s secret has a broker RSA keypair, return the public PEM.

    If the secret already exists and contains a private key, the existing public
    key is returned.  Otherwise a new keypair is generated and the secret is
    created/patched.
    """
    from ...key_pair import generate_key_pair

    check = subprocess.run(
        [
            "kubectl",
            "get",
            "secret",
            infra_secret_name,
            "-n",
            namespace,
            "-o",
            "jsonpath={.data.PRODIGY_TEAMS_PUBLIC_KEY}",
        ],
        capture_output=True,
        text=True,
    )
    if check.returncode == 0 and check.stdout.strip():
        public_pem = base64.b64decode(base64.b64decode(check.stdout.strip())).decode(
            "utf-8"
        )
        msg.info(f"Using existing broker keypair from secret {infra_secret_name}")
        return public_pem

    msg.info("Generating new broker RSA keypair...")
    kp = generate_key_pair()
    private_key_b64 = base64.b64encode(kp.private_pem.encode()).decode()
    public_key_b64 = base64.b64encode(kp.public_pem.encode()).decode()
    cmd = [
        "kubectl",
        "create",
        "secret",
        "generic",
        infra_secret_name,
        f"--namespace={namespace}",
        f"--from-literal=PRODIGY_TEAMS_PRIVATE_KEY={private_key_b64}",
        f"--from-literal=PRODIGY_TEAMS_PUBLIC_KEY={public_key_b64}",
        "--dry-run=client",
        "-o",
        "yaml",
    ]
    create = subprocess.run(cmd, capture_output=True, text=True, check=True)
    subprocess.run(
        ["kubectl", "apply", "-f", "-"],
        input=create.stdout,
        check=True,
        text=True,
    )
    msg.good(f"Created broker keypair in secret {infra_secret_name}")
    return kp.public_pem


def k3s_traefik_exists() -> bool:
    """Check if K3s's bundled Traefik is already running in kube-system."""
    result = subprocess.run(
        ["kubectl", "get", "deployment", "traefik", "-n", "kube-system"],
        capture_output=True,
        text=True,
    )
    return result.returncode == 0


def ensure_traefik(public_ip: str = "") -> None:
    """Install or upgrade Traefik via Helm."""
    if k3s_traefik_exists():
        msg.info(
            "K3s bundled Traefik detected in kube-system — skipping install. "
            "Customise via HelmChartConfig in /var/lib/rancher/k3s/server/manifests/."
        )
        return
    msg.info("Installing / upgrading Traefik...")
    subprocess.run(
        ["helm", "repo", "add", "traefik", "https://traefik.github.io/charts"],
        capture_output=True,
        text=True,
    )
    subprocess.run(
        ["helm", "repo", "update", "traefik"],
        capture_output=True,
        text=True,
    )
    values: dict = {
        "providers": {
            "kubernetesIngress": {"enabled": True},
        },
        "ingressRoute": {"dashboard": {"enabled": False}},
        "ports": {"web": {"port": 80}, "websecure": {"port": 443}},
        "securityContext": {
            "capabilities": {"drop": [], "add": ["NET_BIND_SERVICE"]},
        },
    }
    if public_ip:
        values["service"] = {"spec": {"loadBalancerIP": public_ip}}
    with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
        tmp.write(json.dumps(values))
        tmp_path = tmp.name
    try:
        subprocess.run(
            [
                "helm",
                "upgrade",
                "--install",
                "traefik",
                "traefik/traefik",
                "-n",
                "traefik",
                "--create-namespace",
                "--skip-crds",
                "-f",
                tmp_path,
            ],
            check=True,
            text=True,
        )
    finally:
        Path(tmp_path).unlink(missing_ok=True)
    if public_ip:
        msg.good(f"Traefik installed with static IP {public_ip}")
    else:
        msg.info(
            "Traefik installed. Get the LoadBalancer IP with:\n"
            "  kubectl get svc -n traefik traefik"
            " -o jsonpath='{.status.loadBalancer.ingress[0].ip}'"
        )


def ensure_cert_manager() -> None:
    """Install cert-manager via Helm if not already present."""
    check = subprocess.run(
        ["helm", "status", "cert-manager", "-n", "cert-manager"],
        capture_output=True,
        text=True,
    )
    if check.returncode == 0:
        return
    msg.info("Installing cert-manager...")
    subprocess.run(
        ["helm", "repo", "add", "jetstack", "https://charts.jetstack.io"],
        check=True,
        text=True,
    )
    subprocess.run(["helm", "repo", "update", "jetstack"], check=True, text=True)
    subprocess.run(
        [
            "helm",
            "upgrade",
            "--install",
            "cert-manager",
            "jetstack/cert-manager",
            "-n",
            "cert-manager",
            "--create-namespace",
            "--set",
            "crds.enabled=true",
            "--set",
            "crds.keep=true",
        ],
        check=True,
        text=True,
    )


def get_cluster_auth(client: Client, domain: str) -> BrokerCredentials:
    """Fetch broker credentials from PAM for a given cluster domain."""
    broker = client.broker.read(address=domain)
    return client.broker.credentials(broker_id=broker.id)


def ensure_cluster_record(
    client: Client,
    org_id: UUID,
    domain: str,
    public_key: str,
    *,
    cluster_name: str = "Test Cluster",
    cloud_provider: str = "gcp",
) -> None:
    """Create or update the cluster record in PAM."""
    import pydantic
    from pydantic import AnyUrl

    from prodigy_teams_pam_sdk.errors import BrokerNotFound
    from prodigy_teams_pam_sdk.models import BrokerCreating, BrokerUpdating

    from ...util import URL

    try:
        broker = client.broker.read(address=domain)
    except BrokerNotFound:
        body = BrokerCreating(
            name=cluster_name,
            org_id=org_id,
            address=pydantic.TypeAdapter(AnyUrl).validate_python(URL.parse(domain).url),
            cloud_provider=cloud_provider,
            public_key=public_key,
            state="creating",
            cloud={},
        )
        _ = client.broker.create(body)
    else:
        body = BrokerUpdating(
            id=broker.id,
            name=cluster_name,
            address=domain,
            cloud_provider=cloud_provider,
            public_key=public_key,
            state="creating",
            cloud={},
        )
        _ = client.broker.update(body)
