"""Starburst Enterprise installation recipe (helm install)."""

from __future__ import annotations

import os
import shlex

from k4s.core.executor import Executor, ExecutorError
from k4s.core.products import Step
from k4s.recipes.common.helm import (
    kube_cmd,
    mask_secret,
    postcheck_pods,
    release_exists,
    wait_for_rollout,
    which,
)
from k4s.recipes.common.run import check, q, run
from k4s.recipes.starburst.model import StarburstInstallPlan
from k4s.ui.ui import Ui


# ---------------------------------------------------------------------------
# Shared helpers (used by both install.py and upgrade.py)
# ---------------------------------------------------------------------------

def build_helm_value_args(plan: StarburstInstallPlan) -> list[str]:
    """Build Helm value/set arguments specific to Starburst Enterprise."""
    args: list[str] = []

    if plan.values_files:
        for vf in plan.values_files:
            args += ["-f", vf]

    # License secret reference
    if plan.license_file or plan.license_secret_name:
        secret_name = plan.license_secret_name or "starburst-license"
        args += ["--set", f"starburstPlatformLicense={secret_name}"]

    # Registry credentials
    if plan.registry_username and plan.registry_password:
        args += ["--set", "registryCredentials.enabled=true"]
        args += [
            "--set",
            "registryCredentials.registry=harbor.starburstdata.net/starburstdata",
        ]
        args += ["--set", f"registryCredentials.username={plan.registry_username}"]
        args += ["--set", f"registryCredentials.password={plan.registry_password}"]

    # Common top-level keys
    if plan.environment:
        args += ["--set", f"environment={plan.environment}"]
    if plan.shared_secret:
        args += ["--set", f"sharedSecret={plan.shared_secret}"]

    if plan.set_values:
        for s in plan.set_values:
            args += ["--set", s]
    return args


def build_common_steps(
    ui: Ui,
    ex: Executor,
    plan: StarburstInstallPlan,
    *,
    is_upgrade: bool = False,
) -> list[Step]:
    """Build steps shared by install and upgrade (preflight, namespace, license, registry).

    When *is_upgrade* is True the license warning is suppressed because the
    license secret is expected to exist from the initial installation.
    """

    def _preflight():
        ui.log("Checking kubeconfig, local kubectl/helm, cluster access, and required credentials.")
        if not os.path.exists(plan.kubeconfig_path):
            raise ExecutorError(f"Kubeconfig not found: {plan.kubeconfig_path}")

        if not which(ex, "kubectl"):
            raise ExecutorError("kubectl is not installed. Install it and retry.")
        if not which(ex, "helm"):
            raise ExecutorError("helm is not installed. Install it and retry.")

        # Cluster access (best-effort).
        check(ex, kube_cmd(plan, "kubectl", "version", "--client"))
        rc, _, err = run(ex, kube_cmd(plan, "kubectl", "get", "nodes"))
        if rc != 0:
            raise ExecutorError(f"Cannot access cluster using kubeconfig: {err}")

        # Credentials are required for OCI registry login and chart pull.
        if not plan.repo_username or not plan.repo_password:
            raise ExecutorError(
                "Starburst Harbor credentials are required.\n"
                "Provide --repo-username/--repo-password "
                "(or env vars K4S_STARBURST_REPO_USERNAME/K4S_STARBURST_REPO_PASSWORD)."
            )

        if not plan.chart_version:
            raise ExecutorError(
                "Chart version is required for OCI-based Helm install.\n"
                "Provide --chart-version (e.g. --chart-version 453.0.0).\n"
                "Check available versions: helm search repo starburstdata --versions"
            )

        ui.info(f"OCI chart: {plan.oci_chart_ref}")
        ui.info(f"Chart version: {plan.chart_version}")
        ui.info(f"Repo username: {plan.repo_username} | password: {mask_secret(plan.repo_password)}")

        # License warning only on fresh install; on upgrade the secret
        # is expected to already exist in the cluster.
        if not is_upgrade and not plan.license_file and not plan.license_secret_name:
            ui.warning(
                "No license configured. Starburst Enterprise usually requires a license secret.\n"
                "Provide --license-file to let k4s create the secret, "
                "or set --license-secret-name for an existing secret."
            )

    def _ensure_namespace():
        ui.log("Creating namespace if it does not exist (idempotent).")
        create_cmd = kube_cmd(
            plan, "kubectl", "create", "ns", plan.namespace,
            "--dry-run=client", "-o", "yaml",
        )
        apply_cmd = kube_cmd(plan, "kubectl", "apply", "-f", "-")
        check(ex, f"{create_cmd} | {apply_cmd}")

    def _ensure_license_secret():
        ui.log("Creating or verifying the license secret (idempotent).")
        if not plan.license_file and not plan.license_secret_name:
            return

        secret_name = plan.license_secret_name or "starburst-license"
        if plan.license_file:
            if not os.path.exists(plan.license_file):
                raise ExecutorError(f"License file not found: {plan.license_file}")
            # Idempotent apply (dry-run -> apply). Key must be named starburstdata.license.
            create_cmd = kube_cmd(
                plan, "kubectl", "-n", plan.namespace,
                "create", "secret", "generic", secret_name,
                f"--from-file=starburstdata.license={plan.license_file}",
                "--dry-run=client", "-o", "yaml",
            )
            apply_cmd = kube_cmd(plan, "kubectl", "-n", plan.namespace, "apply", "-f", "-")
            check(ex, f"{create_cmd} | {apply_cmd}")
            return

        # No file, only secret name provided: verify it exists.
        rc, _, _ = run(
            ex,
            kube_cmd(plan, "kubectl", "-n", plan.namespace, "get", "secret", secret_name)
            + " >/dev/null 2>&1",
        )
        if rc != 0:
            raise ExecutorError(
                f"License secret '{secret_name}' not found in namespace '{plan.namespace}'.\n"
                "To create it automatically, provide --license-file <path-to-starburstdata.license>.\n"
                "Or create it manually:\n"
                f"  kubectl -n {plan.namespace} create secret generic {secret_name} "
                "--from-file=starburstdata.license=<license-file>"
            )

    def _registry_login():
        ui.log(f"Logging in to OCI registry {plan.oci_registry}")
        # Use --password-stdin to avoid macOS Keychain "already exists"
        # errors and to keep the password out of process argument lists.
        cmd = (
            f"helm registry login {q(plan.oci_registry)} "
            f"--username {q(plan.repo_username)} "
            f"--password-stdin"
        )
        check(ex, cmd, stdin_data=plan.repo_password)

    return [
        Step(title="Preflight (Starburst)", run=_preflight),
        Step(title=f"Ensure namespace '{plan.namespace}'", run=_ensure_namespace),
        Step(title="Ensure Starburst license secret", run=_ensure_license_secret),
        Step(title="Login to Starburst Harbor registry", run=_registry_login),
    ]


# ---------------------------------------------------------------------------
# Install
# ---------------------------------------------------------------------------

def build_install_steps(
    ui: Ui, ex: Executor, plan: StarburstInstallPlan,
) -> list[Step]:
    """Build Starburst install steps using ``helm install``.

    If the release already exists, raises an error and hints ``k4s upgrade``
    or uninstall + install.
    """
    steps = build_common_steps(ui, ex, plan)

    def _install():
        if release_exists(ex, plan):
            raise ExecutorError(
                f"Helm release '{plan.release_name}' already exists in namespace '{plan.namespace}'.\n"
                "Use `k4s upgrade starburst` to upgrade the existing release,\n"
                "or uninstall it first and rerun install:\n"
                f"  helm uninstall {plan.release_name} -n {plan.namespace}"
            )

        ui.log("Running helm install with OCI chart reference")
        cmd_parts: list[str] = [
            "helm", "install",
            plan.release_name, plan.oci_chart_ref,
            "--version", plan.chart_version,
            "--namespace", plan.namespace,
            "--create-namespace",
        ] + plan.helm_flags() + build_helm_value_args(plan)

        cmd = " ".join(shlex.quote(p) for p in cmd_parts)
        check(ex, cmd)

    steps.append(Step(title=f"Install Starburst release '{plan.release_name}'", run=_install))
    steps.append(Step(title="Wait for rollout to complete", run=lambda: wait_for_rollout(ui, ex, plan)))
    steps.append(Step(title="Post-check (pods)", run=lambda: postcheck_pods(ui, ex, plan)))
    return steps


def build_plan_from_env(plan: StarburstInstallPlan) -> StarburstInstallPlan:
    """Fill credentials from environment variables when not provided."""
    user = plan.repo_username or os.environ.get("K4S_STARBURST_REPO_USERNAME")
    pw = plan.repo_password or os.environ.get("K4S_STARBURST_REPO_PASSWORD")
    reg_user = (
        plan.registry_username
        or os.environ.get("K4S_STARBURST_REGISTRY_USERNAME")
        or user
    )
    reg_pw = (
        plan.registry_password
        or os.environ.get("K4S_STARBURST_REGISTRY_PASSWORD")
        or pw
    )
    return StarburstInstallPlan(
        **{
            **plan.__dict__,
            "repo_username": user,
            "repo_password": pw,
            "registry_username": reg_user,
            "registry_password": reg_pw,
        }
    )
