import logging
import os
import sys
from threading import Thread, Event
from typing import Dict, List, Optional, TYPE_CHECKING
from gefyra.cluster.utils import retrieve_pod_and_container

from .utils import _parse_k8s_cpu_to_cpu_quota, _parse_k8s_mem_to_bytes
from .utils import _inherit_resources_from_workload

if TYPE_CHECKING:
    from docker.models.containers import Container

from gefyra.configuration import (
    ClientConfiguration,
)
from .utils import generate_env_dict_from_strings, stopwatch


logger = logging.getLogger(__name__)

stop_thread = Event()


def _restore_bridges_for_container(
    config: "ClientConfiguration", container: "Container"
) -> None:
    """
    Look up existing GefyraBridges that reference this container by name
    (via the gefyra.dev/client-container label) and update their destinationIP
    to the new container's IP address, triggering the operator-side restore flow.
    """
    from kubernetes.client import ApiException

    container_name = container.name
    try:
        new_ip = container.attrs["NetworkSettings"]["Networks"][config.NETWORK_NAME][
            "IPAddress"
        ]
    except KeyError:
        logger.debug(
            f"Cannot resolve IP for container '{container_name}' on network"
            f" '{config.NETWORK_NAME}', skipping bridge restore"
        )
        return

    try:
        bridges = config.K8S_CUSTOM_OBJECT_API.list_namespaced_custom_object(
            group="gefyra.dev",
            version="v1",
            namespace=config.NAMESPACE,
            plural="gefyrabridges",
            label_selector=f"gefyra.dev/client={config.CLIENT_ID},gefyra.dev/client-container={container_name}",
        )
    except ApiException as e:
        logger.warning(f"Could not query GefyraBridges for restore: {e.reason}")
        return

    for bridge in bridges.get("items", []):
        bridge_name = bridge["metadata"]["name"]
        old_ip = bridge.get("destinationIP", "")
        if old_ip == new_ip:
            logger.debug(
                f"Bridge '{bridge_name}' already has destinationIP={new_ip}, skipping"
            )
            continue
        try:
            config.K8S_CUSTOM_OBJECT_API.patch_namespaced_custom_object(
                group="gefyra.dev",
                version="v1",
                namespace=config.NAMESPACE,
                plural="gefyrabridges",
                name=bridge_name,
                body={"destinationIP": new_ip},
            )
            logger.info(
                f"Restoring bridge '{bridge_name}': updated destinationIP"
                f" from '{old_ip}' to '{new_ip}'"
            )
        except ApiException as e:
            logger.warning(
                f"Could not update destinationIP for GefyraBridge '{bridge_name}': {e.reason}"
            )


def print_logs(container: "Container"):
    for logline in container.logs(stream=True):
        print(logline.decode("utf-8"), end="")


def check_input():
    line = sys.stdin.readline()
    if not line:
        stop_thread.set()


@stopwatch
def run(
    image: str,
    connection_name: str,
    name: str = "",
    command: str = "",
    volumes: Optional[List] = None,
    ports: Optional[Dict] = None,
    detach: bool = True,
    auto_remove: bool = False,
    namespace: str = "",
    env: Optional[List] = None,
    env_from: str = "",
    pull: str = "missing",
    platform: str = "linux/amd64",
    cpu_from: Optional[str] = None,
    memory_from: Optional[str] = None,
    cpu: Optional[str] = None,
    memory: Optional[str] = None,
    security_opts: Optional[List[str]] = None,
    user: Optional[str] = None,
    privileged: Optional[bool] = None,
) -> bool:
    from kubernetes.client import ApiException
    from docker.errors import APIError
    from gefyra.cluster.utils import get_env_from_pod_container
    from gefyra.local.bridge import deploy_app_container
    from gefyra.local.utils import (
        get_processed_paths,
    )
    from gefyra.local.cargo import probe_wireguard_connection

    config = ClientConfiguration(connection_name=connection_name)

    # #125: Fallback to namespace in kube config
    if not namespace:
        from kubernetes.config import kube_config

        _, active_context = kube_config.list_kube_config_contexts()
        namespace = active_context["context"].get("namespace") or "default"
        ns_source = "kubeconfig"
    else:
        ns_source = "--namespace argument"

    dns_search = (
        f"{namespace}.svc.cluster.local svc.cluster.local cluster.local k8s".split(" ")
    )
    #
    # Confirm the wireguard connection working
    #
    try:
        probe_wireguard_connection(config)
    except Exception as e:
        logger.error(e)
        logger.error(
            "\n\033[1m[Hint]\033[0m You may need to run the Cargo Container first."
        )
        return False

    if volumes:
        volumes = get_processed_paths(os.getcwd(), volumes)
    #
    # 1. get the ENV together a) from a K8s container b) from override
    #
    env_dict = {}
    try:
        if env_from:
            env_from_pod, env_from_container = retrieve_pod_and_container(
                env_from, namespace=namespace, config=config
            )
            logger.debug(f"Using ENV from {env_from_pod}/{env_from_container}")
            raw_env = get_env_from_pod_container(
                config, env_from_pod, namespace, env_from_container
            )
            logger.debug("ENV from pod/container is:\n" + raw_env)
            raw_env_vars = raw_env.split("\n")
            env_dict = generate_env_dict_from_strings(raw_env_vars)
    except ApiException as e:
        logger.error(f"Cannot copy environment from Pod: {e.reason} ({e.status}).")
        return False
    if env:
        env_overrides = generate_env_dict_from_strings(env)
        env_dict.update(env_overrides)

    # Inherit CPU/memory from workloads if requested
    inherited_cpu: Optional[str] = None
    inherited_mem: Optional[str] = None
    if cpu_from:
        inherited_cpu, _ = _inherit_resources_from_workload(config, namespace, cpu_from)
    if memory_from:
        _, inherited_mem = _inherit_resources_from_workload(
            config, namespace, memory_from
        )

    final_cpu_qty = cpu or inherited_cpu
    final_mem_qty = memory or inherited_mem

    # Map to Docker-native
    cpu_quota = _parse_k8s_cpu_to_cpu_quota(final_cpu_qty) if final_cpu_qty else None
    mem_limit = _parse_k8s_mem_to_bytes(final_mem_qty) if final_mem_qty else None

    #
    # 2. deploy the requested container to Gefyra
    #
    try:
        container = deploy_app_container(
            config=config,
            image=image,
            name=name,
            command=command,
            ports=ports,
            env=env_dict,
            dns_search=dns_search,
            auto_remove=auto_remove,
            volumes=volumes,
            pull=pull,
            platform=platform,
            cpu_quota=cpu_quota,
            mem_limit=mem_limit,
            security_opts=security_opts,
            user=user,
            privileged=privileged,
        )
    except APIError as e:
        if e.status_code == 409:
            logger.error(e.explanation)
            logger.error(
                f"\n\033[1m[Hint]\033[0m You could remove the container via: \ndocker rm {name}"
            )
            return True
        else:
            raise RuntimeError(e.explanation)

    logger.info(
        f"Container image '{', '.join(container.image.tags)}' started with name"
        f" '{container.name}' in Kubernetes namespace '{namespace}' (from {ns_source})"
    )

    # Restore existing bridges that target this container name
    _restore_bridges_for_container(config, container)

    if detach:
        return True
    else:
        try:
            logger.debug("Now printing out logs")
            t = Thread(target=print_logs, args=[container], daemon=True)
            t.start()
            input_thread = Thread(target=check_input, daemon=True)
            input_thread.start()
            while t.is_alive():
                if stop_thread.is_set():
                    logger.info(f"Detached from container: {name}")
                    return True
        except KeyboardInterrupt:
            container.stop()
            logger.info(f"Container stopped: {name}")
    return True
