"""Datafloem installation recipe (helm install, repo-based chart)."""

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.datafloem.model import DatafloemInstallPlan
from k4s.ui.ui import Ui


def build_plan_from_env(plan: DatafloemInstallPlan) -> DatafloemInstallPlan:
    """Fill repo/registry credentials and JWT secret from environment variables when not provided."""
    user = plan.repo_username or os.environ.get("K4S_DATAFLOEM_REPO_USERNAME")
    pw = plan.repo_password or os.environ.get("K4S_DATAFLOEM_REPO_PASSWORD")
    reg_user = (
        plan.registry_username
        or os.environ.get("K4S_DATAFLOEM_REGISTRY_USERNAME")
        or user
    )
    reg_pw = (
        plan.registry_password
        or os.environ.get("K4S_DATAFLOEM_REGISTRY_PASSWORD")
        or pw
    )
    jwt = plan.jwt_secret or os.environ.get("K4S_DATAFLOEM_JWT_SECRET")
    return DatafloemInstallPlan(
        **{
            **plan.__dict__,
            "repo_username": user,
            "repo_password": pw,
            "registry_username": reg_user,
            "registry_password": reg_pw,
            "jwt_secret": jwt,
        }
    )


def build_helm_value_args(plan: DatafloemInstallPlan) -> list[str]:
    """Build Helm value/set arguments for Datafloem."""
    args: list[str] = []
    if plan.chart_version:
        args += ["--version", plan.chart_version]
    if plan.values_files:
        for vf in plan.values_files:
            args += ["-f", vf]

    # Ensure the chart uses the image pull secret we manage.
    # Datafloem docs define imagePullSecrets as:
    #   imagePullSecrets:
    #     - name: SECRET_NAME
    if plan.image_pull_secret_name:
        args += ["--set", f"frontend.imagePullSecrets[0].name={plan.image_pull_secret_name}"]
        args += ["--set", f"backend.imagePullSecrets[0].name={plan.image_pull_secret_name}"]
        args += ["--set", f"operator.imagePullSecrets[0].name={plan.image_pull_secret_name}"]

    # Force image repositories to the configured docker registry server.
    # The chart defaults to registry.datafloem.com:30443 which can fail with TLS issues.
    reg = plan.registry_server.rstrip("/")
    args += ["--set", f"frontend.image.repository={reg}/datafloem-frontend"]
    args += ["--set", f"backend.image.repository={reg}/datafloem-backend-amd64"]
    args += ["--set", f"backend.metaBrowser.image.repository={reg}/datafloem-meta-browser-x86_64"]
    args += ["--set", f"operator.image.repository={reg}/datafloem-operator-amd64"]

    # User-provided overrides should win over our defaults.
    if plan.set_values:
        for s in plan.set_values:
            args += ["--set", s]
    return args


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

    def _preflight():
        ui.log("Checking kubeconfig, local kubectl/helm, and cluster access.")
        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.")

        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}")

        if not plan.repo_username or not plan.repo_password:
            raise ExecutorError(
                "Datafloem Helm repository credentials are required.\n"
                "Provide --repo-username/--repo-password "
                "(or env vars K4S_DATAFLOEM_REPO_USERNAME/K4S_DATAFLOEM_REPO_PASSWORD)."
            )

        if not plan.chart_version:
            raise ExecutorError(
                "Chart version is required.\n"
                "Provide --chart-version (pin versions for deterministic installs)."
            )

        ui.info(f"Helm repo: {plan.repo_url}")
        ui.info(f"Repo username: {plan.repo_username} | password: {mask_secret(plan.repo_password)}")
        ui.info(f"Chart version: {plan.chart_version}")
        ui.info(f"Image pull secret: {plan.image_pull_secret_name}")
        ui.info(f"Docker registry: {plan.registry_server}")

    def _ensure_image_pull_secret():
        ui.log("Creating or verifying the image pull secret (idempotent).")
        name = plan.image_pull_secret_name
        if plan.registry_username and plan.registry_password:
            create_cmd = kube_cmd(
                plan,
                "kubectl",
                "-n",
                plan.namespace,
                "create",
                "secret",
                "docker-registry",
                name,
                f"--docker-server={plan.registry_server}",
                f"--docker-username={plan.registry_username}",
                f"--docker-password={plan.registry_password}",
                "--dry-run=client",
                "-o",
                "yaml",
            )
            apply_cmd = kube_cmd(plan, "kubectl", "-n", plan.namespace, "apply", "-f", "-")
            check(ex, f"{create_cmd} | {apply_cmd}", silent=True)
            return

        rc, _, _ = run(
            ex,
            kube_cmd(plan, "kubectl", "-n", plan.namespace, "get", "secret", name)
            + " >/dev/null 2>&1",
            silent=True,
        )
        if rc != 0:
            raise ExecutorError(
                f"Image pull secret '{name}' not found in namespace '{plan.namespace}'.\n"
                "Provide --registry-username/--registry-password to let k4s create it,\n"
                "or create it manually:\n"
                f"  kubectl -n {plan.namespace} create secret docker-registry {name} "
                f"--docker-server={plan.registry_server} "
                "--docker-username=<user> --docker-password=<password>"
            )

    def _ensure_jwt_secret():
        """Create or verify the JWT secret used by the Datafloem backend."""
        name = plan.jwt_secret_name
        if plan.jwt_secret:
            ui.log(f"Creating JWT secret '{name}' (idempotent).")
            create_cmd = kube_cmd(
                plan,
                "kubectl",
                "-n",
                plan.namespace,
                "create",
                "secret",
                "generic",
                name,
                f"--from-literal=JWT_SECRET={plan.jwt_secret}",
                "--dry-run=client",
                "-o",
                "yaml",
            )
            apply_cmd = kube_cmd(plan, "kubectl", "-n", plan.namespace, "apply", "-f", "-")
            check(ex, f"{create_cmd} | {apply_cmd}", silent=True)
            return

        rc, _, _ = run(
            ex,
            kube_cmd(plan, "kubectl", "-n", plan.namespace, "get", "secret", name)
            + " >/dev/null 2>&1",
            silent=True,
        )
        if rc != 0:
            if not is_upgrade:
                raise ExecutorError(
                    f"JWT secret '{name}' not found in namespace '{plan.namespace}'.\n"
                    "Provide --jwt-secret to let k4s create it,\n"
                    "(or env var K4S_DATAFLOEM_JWT_SECRET),\n"
                    "or create it manually:\n"
                    f"  kubectl -n {plan.namespace} create secret generic {name} "
                    "--from-literal=JWT_SECRET=<your-secret>"
                )
            ui.warning(
                f"JWT secret '{name}' not found. Skipping (expected to exist from initial install)."
            )

    def _ensure_license_secret():
        """Create or verify the license secret used by the Datafloem operator."""
        name = plan.license_secret_name
        if plan.license_file:
            if not os.path.exists(plan.license_file):
                raise ExecutorError(f"License file not found: {plan.license_file}")
            ui.log(f"Creating license secret '{name}' from file (idempotent).")
            create_cmd = kube_cmd(
                plan,
                "kubectl",
                "-n",
                plan.namespace,
                "create",
                "secret",
                "generic",
                name,
                f"--from-file=dfl.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}", silent=True)
            return

        rc, _, _ = run(
            ex,
            kube_cmd(plan, "kubectl", "-n", plan.namespace, "get", "secret", name)
            + " >/dev/null 2>&1",
            silent=True,
        )
        if rc != 0:
            if not is_upgrade:
                raise ExecutorError(
                    f"License secret '{name}' not found in namespace '{plan.namespace}'.\n"
                    "Provide --license-file to let k4s create it,\n"
                    "or create it manually:\n"
                    f"  kubectl -n {plan.namespace} create secret generic {name} "
                    "--from-file=dfl.license=<path-to-license>"
                )
            ui.warning(
                f"License secret '{name}' not found. Skipping (expected to exist from initial install)."
            )

    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 _repo_add_update():
        ui.log("Adding/updating Datafloem Helm repo and updating index.")
        cmd = (
            "helm repo add --force-update "
            f"{q(plan.repo_name)} {q(plan.repo_url)} "
            f"--username {q(plan.repo_username)} "
            f"--password {q(plan.repo_password)}"
        )
        check(ex, cmd, silent=True)
        check(ex, "helm repo update", silent=True)

    return [
        Step(title="Preflight (Datafloem)", run=_preflight),
        Step(title=f"Ensure namespace '{plan.namespace}'", run=_ensure_namespace),
        Step(title="Ensure image pull secret", run=_ensure_image_pull_secret),
        Step(title="Ensure JWT secret", run=_ensure_jwt_secret),
        Step(title="Ensure license secret", run=_ensure_license_secret),
        Step(title="Add/update Helm repository", run=_repo_add_update),
    ]


def build_install_steps(ui: Ui, ex: Executor, plan: DatafloemInstallPlan) -> list[Step]:
    """Build Datafloem install steps using ``helm 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 datafloem` to upgrade the existing release,\n"
                "or uninstall it first and rerun install:\n"
                f"  helm uninstall {plan.release_name} -n {plan.namespace}"
            )

        chart_ref = f"{plan.repo_name}/{plan.chart_name}"
        ui.log("Running helm install for Datafloem")
        cmd_parts: list[str] = [
            "helm",
            "install",
            plan.release_name,
            chart_ref,
            "--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 Datafloem 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

