from __future__ import annotations

import re
import subprocess
import time
from os import PathLike
from pathlib import Path

from vantage6.common import error, info
from vantage6.common.context import AppContext
from vantage6.common.globals import (
    DEFAULT_CHART_REPO,
    LOCALHOST,
    InstanceType,
)

from vantage6.cli.common.utils import check_running
from vantage6.cli.globals import ChartName
from vantage6.cli.utils import check_config_name_allowed, validate_input_cmd_args


def prestart_checks(
    ctx: AppContext,
    instance_type: InstanceType,
    name: str,
    system_folders: bool,
) -> None:
    """
    Run pre-start checks for an instance.
    """

    check_config_name_allowed(name)

    if check_running(ctx.helm_release_name, instance_type, name, system_folders):
        error(f"Instance '{name}' is already running.")
        exit(1)


def helm_install(
    release_name: str,
    chart_name: ChartName,
    values_file: str | PathLike | None = None,
    context: str | None = None,
    namespace: str | None = None,
    local_chart_dir: str | None = None,
    custom_values: list[str] | None = None,
) -> None:
    """
    Manage the `helm install` command.

    Parameters
    ----------
    release_name : str
        The name of the Helm release.
    chart_name : str
        The name of the Helm chart.
    values_file : str, optional
        A single values file to use with the `-f` flag.
    context : str, optional
        The Kubernetes context to use.
    namespace : str, optional
        The Kubernetes namespace to use.
    local_chart_dir : str, optional
        The local directory containing the Helm charts.
    custom_values : list[str], optional
        Custom values to pass to the Helm chart, that override the values in the values
        file. Each item in the list is a string in the format "key=value".
    """
    # Input validation
    validate_input_cmd_args(release_name, "release name")
    validate_input_cmd_args(chart_name, "chart name")

    values_file = Path(values_file) if values_file else None
    if values_file and not values_file.is_file():
        error(f"Helm chart values file does not exist: {values_file}")
        return

    validate_input_cmd_args(context, "context name", allow_none=True)
    validate_input_cmd_args(namespace, "namespace name", allow_none=True)

    if local_chart_dir and local_chart_dir.rstrip("/").endswith(chart_name.value):
        local_chart_dir = str(Path(local_chart_dir).parent)

    # Create the command
    if local_chart_dir:
        command = [
            "helm",
            "install",
            release_name,
            f"{local_chart_dir}/{chart_name.value}",
        ]
    else:
        command = [
            "helm",
            "install",
            release_name,
            chart_name,
            "--repo",
            DEFAULT_CHART_REPO,
            # TODO v5+ remove this flag when we have a stable release
            "--devel",
        ]

    if values_file:
        command.extend(["-f", str(values_file)])

    if context:
        command.extend(["--kube-context", context])

    if namespace:
        command.extend(["--namespace", namespace])

    if custom_values:
        for custom_value in custom_values:
            command.extend(["--set", custom_value])

    try:
        subprocess.run(
            command,
            stdout=subprocess.DEVNULL,
            check=True,
        )
        info(
            f"Successfully installed release '{release_name}' using chart "
            f"'{chart_name.value}'."
        )
    except subprocess.CalledProcessError:
        error(f"Failed to install release '{release_name}'.")
        exit(1)
    except FileNotFoundError:
        error(
            "Helm command not found. Please ensure Helm is installed and available in "
            "the PATH."
        )
        exit(1)


def start_port_forward(
    service_name: str,
    service_port: int,
    port: int,
    ip: str = LOCALHOST,
    context: str | None = None,
    namespace: str | None = None,
) -> None:
    """
    Port forward a kubernetes service.

    Parameters
    ----------
    service_name : str
        The name of the Kubernetes service to port forward.
    service_port : int
        The port on the service to forward.
    port : int
        The port to listen on.
    ip : str
        The IP address to listen on. Defaults to localhost.
    context : str | None
        The Kubernetes context to use.
    namespace : str | None
        The Kubernetes namespace to use.
    """
    # Input validation
    validate_input_cmd_args(service_name, "service name")
    if not isinstance(service_port, int) or service_port <= 0:
        error(f"Invalid service port: {service_port}. Must be a positive integer.")
        return

    if not isinstance(port, int) or port <= 0:
        error(f"Invalid local port: {port}. Must be a positive integer.")
        return

    if ip and not re.match(
        r"^(localhost|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$", ip
    ):
        error(f"Invalid IP address: {ip}. Must be a valid IPv4 address or 'localhost'.")
        return

    validate_input_cmd_args(context, "context name", allow_none=True)
    validate_input_cmd_args(namespace, "namespace name", allow_none=True)

    # Check if the service is ready before starting port forwarding
    info(f"Waiting for service '{service_name}' to become ready...")
    start_time = time.time()
    timeout = 300  # seconds
    while time.time() - start_time < timeout:
        try:
            command = [
                "kubectl",
                "get",
                "endpoints",
                service_name,
                "-o",
                "jsonpath={.subsets[*].addresses[*].ip}",
            ]

            if context:
                command.extend(["--context", context])

            if namespace:
                command.extend(["--namespace", namespace])

            result = subprocess.check_output(command).decode().strip()

            if result:
                info(f"Service '{service_name}' is ready.")
                break
        except subprocess.CalledProcessError:
            pass  # ignore and retry

        time.sleep(2)
    else:
        error(
            f"Timeout: Service '{service_name}' has no ready endpoints after {timeout} "
            "seconds."
        )
        return

    # Create the port forwarding command
    if not ip:
        ip = LOCALHOST

    command = [
        "kubectl",
        "port-forward",
        "--address",
        ip,
        f"service/{service_name}",
        f"{port}:{service_port}",
    ]

    if context:
        command.extend(["--context", context])

    if namespace:
        command.extend(["--namespace", namespace])

    # Start the port forwarding process
    try:
        process = subprocess.Popen(
            command,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.PIPE,
            start_new_session=True,  # Start in new session to detach from parent
        )

        # Give the process a moment to start and check if it's still running
        time.sleep(1)
        if process.poll() is not None:
            # Process has already terminated
            e = process.stderr.read().decode() if process.stderr else "Unknown error"
            error(f"Failed to start port forwarding: {e}")
            return

        info(
            f"Port forwarding started: {ip}:{port} -> {service_name}:{service_port} "
            f"(PID: {str(process.pid)})"
        )
        return
    except Exception as e:
        error(f"Failed to start port forwarding: {e}")
        return
