from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable, Awaitable
import threading
import inspect
import uuid

from flownet.core.request import FlownetPacket
from flownet.runtime.node_control import (
    NODE_CONTROL_PACKET_TYPE,
    decode_node_control_response,
    resolve_node_control_op,
)


@dataclass(frozen=True)
class RuntimeTaskServices:
    submit_runtime_coro: Callable[[Any], Any]
    submit_user_coro: Callable[[Any], Any] | None = None

    def submit(self, coro: Awaitable[Any], *, loop: str = "runtime"):
        if loop == "user" and self.submit_user_coro is not None:
            return self.submit_user_coro(coro)
        return self.submit_runtime_coro(coro)


@dataclass(frozen=True)
class RuntimeActorServices:
    dispatcher: Any | None
    local_address_fn: Callable[[], str]
    resolve_actor_fn: Callable[[str], Any]
    create_actor_fn: Callable[..., Any] | None = None
    make_object_actor_fn: Callable[..., Any] | None = None
    register_actor_fn: Callable[..., Any] | None = None
    lookup_actor_fn: Callable[[str], Any] | None = None

    def local_address(self) -> str:
        return self.local_address_fn()

    def resolve(self, actor_id: str) -> Any:
        return self.resolve_actor_fn(actor_id)

    def lookup(self, actor_id: str) -> Any:
        if self.lookup_actor_fn is None:
            raise RuntimeError("Actor lookup not configured.")
        return self.lookup_actor_fn(actor_id)

    async def create_actor(
        self,
        actor_class: type,
        *args,
        actor_config: Any | None = None,
        **kwargs,
    ) -> Any:
        if self.create_actor_fn is None:
            raise RuntimeError("Actor creation not configured.")
        return await self.create_actor_fn(
            actor_class,
            *args,
            actor_config=actor_config,
            **kwargs,
        )

    def make_object_actor(self, actor_object: Any, actor_config: Any | None = None) -> Any:
        if self.make_object_actor_fn is None:
            raise RuntimeError("Actor factory not configured.")
        return self.make_object_actor_fn(actor_object, actor_config=actor_config)

    def register_actor(
        self,
        actor_id: str,
        actor_object: Any,
        actor_config: Any | None = None,
    ) -> Any:
        if self.register_actor_fn is None:
            raise RuntimeError("Actor registration not configured.")
        return self.register_actor_fn(
            actor_id,
            actor_object,
            actor_config=actor_config,
        )

    async def call(self, rmf, *args, **kwargs) -> Any:
        if self.dispatcher is None:
            raise RuntimeError("Actor dispatcher not configured.")
        return await self.dispatcher.handle_actor_call(rmf, *args, **kwargs)


@dataclass(frozen=True)
class RuntimeTopicServices:
    service_registry: Any | None
    topic_registry: Any | None
    service_topic_prefix: str = "service:"

    def require_service_registry(self):
        if self.service_registry is None:
            raise RuntimeError("Service registry not configured.")
        return self.service_registry

    def require_topic_registry(self):
        if self.topic_registry is None:
            raise RuntimeError("Topic registry not configured.")
        return self.topic_registry

    def emit_service_event(
        self,
        service_name: str,
        event_type: str,
        payload: Any = None,
        tags: dict[str, str] | None = None,
    ) -> None:
        self.require_service_registry().publish(service_name, event_type, payload, tags)

    def emit_topic_event(
        self,
        topic_id: str,
        event_type: str,
        payload: Any = None,
        tags: dict[str, str] | None = None,
    ) -> None:
        if isinstance(topic_id, str) and topic_id.startswith(self.service_topic_prefix):
            raise ValueError(
                "Direct topic publish to reserved service namespace is not supported. "
                "Use emit_service_event(service_name, ...) instead."
            )
        self.require_topic_registry().publish(topic_id, event_type, payload, tags)


@dataclass(frozen=True)
class RuntimeSourceServices:
    registry: Any

    def start_source(self, source_id: str, source_spec: Any, *args, **kwargs):
        return self.registry.start_source(source_id, source_spec, *args, **kwargs)

    def stop_source(self, source_id: str) -> bool:
        return self.registry.stop_source(source_id)

    def list_sources(self) -> list[dict[str, Any]]:
        return self.registry.list_sources()


@dataclass(frozen=True)
class RuntimeIOService:
    call_manager: Any

    async def send_packet(self, address: str, packet: Any) -> Any:
        return await self.call_manager.send_packet(address, packet)

    def register_call(self, call_uuid: str):
        return self.call_manager.register(call_uuid)


@dataclass(frozen=True)
class RuntimeStateServices:
    backend: Any
    log_fn: Callable[[str], None] = print

    def get(
        self,
        namespace: str,
        key: str,
        *,
        default: Any = None,
    ) -> Any:
        try:
            value = self.backend.get(namespace, key)
            if value is None:
                return default
            return value
        except Exception as exc:  # pragma: no cover - fail-open safety net
            self.log_fn(f"[RuntimeStateServices] get failed (fail-open): {exc}")
            return default

    def put(
        self,
        namespace: str,
        key: str,
        value: Any,
        *,
        ttl_seconds: float | None = None,
    ) -> bool:
        try:
            self.backend.put(
                namespace,
                key,
                value,
                ttl_seconds=ttl_seconds,
            )
            return True
        except Exception as exc:  # pragma: no cover - fail-open safety net
            self.log_fn(f"[RuntimeStateServices] put failed (fail-open): {exc}")
            return False

    def delete(self, namespace: str, key: str) -> bool:
        try:
            self.backend.delete(namespace, key)
            return True
        except Exception as exc:  # pragma: no cover - fail-open safety net
            self.log_fn(f"[RuntimeStateServices] delete failed (fail-open): {exc}")
            return False

    def scan(
        self,
        namespace: str,
        *,
        prefix: str | None = None,
        limit: int | None = None,
    ) -> list[tuple[str, Any]]:
        try:
            return list(self.backend.scan(namespace, prefix=prefix, limit=limit))
        except Exception as exc:  # pragma: no cover - fail-open safety net
            self.log_fn(f"[RuntimeStateServices] scan failed (fail-open): {exc}")
            return []

    def close(self) -> None:
        close_fn = getattr(self.backend, "close", None)
        if callable(close_fn):
            close_fn()


@dataclass(frozen=True)
class RuntimeFlowServices:
    handle_flow_request: Callable[[Any], Any] | None
    submit_runtime_coro: Callable[[Any], Any]
    submit_flow_fn: Callable[..., Any] | None = None
    lookup_run_fn: Callable[[str], Any] | None = None
    register_flow_program_fn: Callable[..., Any] | None = None
    resolve_flow_program_payload_fn: Callable[..., Any] | None = None
    list_flow_programs_fn: Callable[..., Any] | None = None
    get_flow_program_fn: Callable[..., Any] | None = None
    delete_flow_program_fn: Callable[..., Any] | None = None
    submit_flow_program_fn: Callable[..., Any] | None = None
    stream_tracker_stats_fn: Callable[[], dict[str, Any]] | None = None
    stream_tracker_request_status_fn: Callable[[str], dict[str, Any]] | None = None

    async def handle(self, request: Any) -> Any:
        if self.handle_flow_request is None:
            raise RuntimeError("Flow request handler not configured.")
        return await self.handle_flow_request(request)

    def submit(self, request: Any):
        if self.handle_flow_request is None:
            raise RuntimeError("Flow request handler not configured.")
        return self.submit_runtime_coro(self.handle_flow_request(request))

    def submit_flow(
        self,
        flow_def_or_task,
        *,
        ingress: Any = None,
        egress: Any = None,
        run_config: dict[str, Any] | None = None,
    ):
        from flownet.runtime.contexts.actor import system_calls_allowed

        if not system_calls_allowed():
            raise RuntimeError(
                "Direct RuntimeFlowServices.submit_flow() is not allowed in user execution scope. "
                "Use flownet.api.runtime.submit_flow(...) instead."
            )
        if self.submit_flow_fn is None:
            raise RuntimeError("Flow submit facade not configured.")
        return self.submit_flow_fn(
            flow_def_or_task,
            ingress=ingress,
            egress=egress,
            run_config=run_config,
        )

    def lookup_run(self, run_id: str):
        if self.lookup_run_fn is None:
            raise RuntimeError("Flow run lookup not configured.")
        return self.lookup_run_fn(run_id)

    def register_flow_program(
        self,
        flow_def_or_task,
        *,
        flow_uri: str,
        version: str,
        metadata: dict[str, Any] | None = None,
        io_contract: dict[str, Any] | None = None,
    ):
        from flownet.runtime.contexts.actor import system_calls_allowed

        if not system_calls_allowed():
            raise RuntimeError(
                "Direct RuntimeFlowServices.register_flow_program() is not allowed in user execution scope. "
                "Use flownet.api.runtime.register_flow_program(...) instead."
            )
        if self.register_flow_program_fn is None:
            raise RuntimeError("Flow program registration facade not configured.")
        return self.register_flow_program_fn(
            flow_def_or_task,
            flow_uri=flow_uri,
            version=version,
            metadata=metadata,
            io_contract=io_contract,
        )

    def resolve_flow_program_payload(self, flow_uri: str, *, version: str):
        if self.resolve_flow_program_payload_fn is None:
            raise RuntimeError("Flow program payload resolver not configured.")
        return self.resolve_flow_program_payload_fn(flow_uri, version=version)

    def list_flow_programs(
        self,
        *,
        prefix: str | None = None,
        tags: dict[str, Any] | None = None,
        limit: int | None = None,
    ):
        if self.list_flow_programs_fn is None:
            raise RuntimeError("Flow program list facade not configured.")
        return self.list_flow_programs_fn(prefix=prefix, tags=tags, limit=limit)

    def get_flow_program(self, flow_uri: str, version: str | None = None):
        if self.get_flow_program_fn is None:
            raise RuntimeError("Flow program get facade not configured.")
        return self.get_flow_program_fn(flow_uri, version=version)

    def delete_flow_program(self, flow_uri: str, version: str | None = None):
        from flownet.runtime.contexts.actor import system_calls_allowed

        if not system_calls_allowed():
            raise RuntimeError(
                "Direct RuntimeFlowServices.delete_flow_program() is not allowed in user execution scope. "
                "Use flownet.api.runtime.delete_flow_program(...) instead."
            )
        if self.delete_flow_program_fn is None:
            raise RuntimeError("Flow program delete facade not configured.")
        return self.delete_flow_program_fn(flow_uri, version=version)

    def submit_flow_program(
        self,
        flow_uri: str,
        version: str | None = None,
        *,
        bindings: dict[str, Any] | None = None,
        ingress: Any = None,
        egress: Any = None,
        run_config: dict[str, Any] | None = None,
    ):
        from flownet.runtime.contexts.actor import system_calls_allowed

        if not system_calls_allowed():
            raise RuntimeError(
                "Direct RuntimeFlowServices.submit_flow_program() is not allowed in user execution scope. "
                "Use flownet.api.runtime.submit_flow_program(...) instead."
            )
        if self.submit_flow_program_fn is None:
            raise RuntimeError("Flow program submit facade not configured.")
        return self.submit_flow_program_fn(
            flow_uri,
            version=version,
            bindings=bindings,
            ingress=ingress,
            egress=egress,
            run_config=run_config,
        )

    def stream_tracker_stats(self):
        if self.stream_tracker_stats_fn is None:
            raise RuntimeError("Stream tracker stats are not configured.")
        return self.stream_tracker_stats_fn()

    def stream_tracker_request_status(self, request_id: str):
        if self.stream_tracker_request_status_fn is None:
            raise RuntimeError("Stream tracker request status is not configured.")
        return self.stream_tracker_request_status_fn(request_id)


@dataclass(frozen=True)
class RuntimeClusterServices:
    view: Any
    resolve_plan_fn: Callable[..., Any] | None = None
    metrics_snapshot_fn: Callable[[], Any] | None = None
    reset_metrics_fn: Callable[[], Any] | None = None
    gossip_stats_fn: Callable[[], Any] | None = None

    def get_view(self):
        return self.view

    def resolve_node_for_key(self, key: str, affinity_hint: str | None = None):
        return self.view.resolve_node_for_key(key, affinity_hint)

    def resolve_plan(self, **kwargs):
        if self.resolve_plan_fn is None:
            raise RuntimeError("Cluster plan resolver not configured.")
        return self.resolve_plan_fn(**kwargs)

    def metrics_snapshot(self):
        if self.metrics_snapshot_fn is None:
            raise RuntimeError("Cluster metrics snapshot is not configured.")
        return self.metrics_snapshot_fn()

    def reset_metrics(self):
        if self.reset_metrics_fn is None:
            raise RuntimeError("Cluster metrics reset is not configured.")
        return self.reset_metrics_fn()

    def gossip_stats(self):
        if self.gossip_stats_fn is None:
            raise RuntimeError("Cluster gossip stats is not configured.")
        return self.gossip_stats_fn()


@dataclass(frozen=True)
class RuntimeNodeControlServices:
    service: Any | None = None
    call_manager: Any | None = None
    local_address_fn: Callable[[], str] | None = None
    call_via_v1_fn: Callable[[str, str, tuple[Any, ...], dict[str, Any]], Awaitable[Any]] | None = None
    v1_fail_open_legacy: bool = True

    def require_service(self):
        if self.service is None:
            raise RuntimeError("Runtime node control service not configured.")
        return self.service

    def local_address(self) -> str:
        if self.local_address_fn is None:
            raise RuntimeError("Node control local address resolver not configured.")
        return self.local_address_fn()

    def invoke(self, op: str, *args, **kwargs) -> Any:
        return self.require_service().invoke(op, *args, **kwargs)

    async def handle_packet(self, payload: Any) -> dict[str, Any]:
        return await resolve_node_control_op(self.require_service(), payload)

    async def call_on(self, address: str, op: str, *args, **kwargs) -> Any:
        target = str(address or "").strip()
        if not target:
            raise ValueError("node control target address must not be empty.")

        local_address = self.local_address()
        if target == local_address:
            result = self.invoke(op, *args, **kwargs)
            if inspect.isawaitable(result):
                result = await result
            return result

        if self.call_via_v1_fn is not None:
            try:
                return await self.call_via_v1_fn(
                    target,
                    op,
                    tuple(args),
                    dict(kwargs),
                )
            except Exception:
                if not self.v1_fail_open_legacy:
                    raise

        if self.call_manager is None:
            raise RuntimeError("Node control call manager not configured.")
        response = await self.call_manager.send_packet(
            target,
            FlownetPacket(
                type=NODE_CONTROL_PACKET_TYPE,
                uuid=uuid.uuid4().hex,
                reply=True,
                packet_content={
                    "reply_to": local_address,
                    "op": op,
                    "args": list(args),
                    "kwargs": dict(kwargs),
                },
            ),
        )
        return decode_node_control_response(response, op=op, address=target)


@dataclass
class RuntimeKernel:
    actors: RuntimeActorServices | None = None
    tasks: RuntimeTaskServices | None = None
    topics: RuntimeTopicServices | None = None
    sources: RuntimeSourceServices | None = None
    io: RuntimeIOService | None = None
    state: RuntimeStateServices | None = None
    flows: RuntimeFlowServices | None = None
    cluster: RuntimeClusterServices | None = None
    node_control: RuntimeNodeControlServices | None = None

    def require_actors(self) -> RuntimeActorServices:
        if self.actors is None:
            raise RuntimeError("Runtime actors service not configured.")
        return self.actors

    def require_tasks(self) -> RuntimeTaskServices:
        if self.tasks is None:
            raise RuntimeError("Runtime tasks service not configured.")
        return self.tasks

    def require_topics(self) -> RuntimeTopicServices:
        if self.topics is None:
            raise RuntimeError("Runtime topics service not configured.")
        return self.topics

    def require_sources(self) -> RuntimeSourceServices:
        if self.sources is None:
            raise RuntimeError("Runtime source service not configured.")
        return self.sources

    def require_io(self) -> RuntimeIOService:
        if self.io is None:
            raise RuntimeError("Runtime IO service not configured.")
        return self.io

    def require_state(self) -> RuntimeStateServices:
        if self.state is None:
            raise RuntimeError("Runtime state service not configured.")
        return self.state

    def require_flows(self) -> RuntimeFlowServices:
        if self.flows is None:
            raise RuntimeError("Runtime flow service not configured.")
        return self.flows

    def require_cluster(self) -> RuntimeClusterServices:
        if self.cluster is None:
            raise RuntimeError("Runtime cluster service not configured.")
        return self.cluster

    def require_node_control(self) -> RuntimeNodeControlServices:
        if self.node_control is None:
            raise RuntimeError("Runtime node control service not configured.")
        return self.node_control

    def local_address(self) -> str:
        return self.require_actors().local_address()

    def require_service_registry(self):
        return self.require_topics().require_service_registry()

    def require_topic_registry(self):
        return self.require_topics().require_topic_registry()

    def start_service(self, name: str, service_spec: Any, *args, **kwargs):
        return self.require_service_registry().start_service(name, service_spec, *args, **kwargs)

    def stop_service(self, name: str) -> bool:
        return self.require_service_registry().stop_service(name)

    def list_services(self) -> list[dict[str, Any]]:
        return self.require_service_registry().list_services()

    def start_source(self, source_id: str, source_spec: Any, *args, **kwargs):
        return self.require_sources().start_source(source_id, source_spec, *args, **kwargs)

    def stop_source(self, source_id: str) -> bool:
        return self.require_sources().stop_source(source_id)

    def list_sources(self) -> list[dict[str, Any]]:
        return self.require_sources().list_sources()

    def cluster_view(self):
        return self.require_cluster().get_view()


class RuntimeKernelBuilder:
    def __init__(self, kernel: RuntimeKernel | None = None):
        self._kernel = kernel or RuntimeKernel()

    @property
    def kernel(self) -> RuntimeKernel:
        return self._kernel

    def register_actors(self, actors: RuntimeActorServices) -> RuntimeKernel:
        self._kernel.actors = actors
        return self._kernel

    def register_tasks(self, tasks: RuntimeTaskServices) -> RuntimeKernel:
        self._kernel.tasks = tasks
        return self._kernel

    def register_topics(self, topics: RuntimeTopicServices) -> RuntimeKernel:
        self._kernel.topics = topics
        return self._kernel

    def register_sources(self, sources: RuntimeSourceServices) -> RuntimeKernel:
        self._kernel.sources = sources
        return self._kernel

    def register_io(self, io: RuntimeIOService) -> RuntimeKernel:
        self._kernel.io = io
        return self._kernel

    def register_state(self, state: RuntimeStateServices) -> RuntimeKernel:
        self._kernel.state = state
        return self._kernel

    def register_flows(self, flows: RuntimeFlowServices) -> RuntimeKernel:
        self._kernel.flows = flows
        return self._kernel

    def register_cluster(self, cluster: RuntimeClusterServices) -> RuntimeKernel:
        self._kernel.cluster = cluster
        return self._kernel

    def register_node_control(self, node_control: RuntimeNodeControlServices) -> RuntimeKernel:
        self._kernel.node_control = node_control
        return self._kernel

    def build(self) -> RuntimeKernel:
        return self._kernel


_kernel_lock = threading.Lock()
_kernel: RuntimeKernel | None = None


def register_kernel(kernel: RuntimeKernel) -> None:
    global _kernel
    with _kernel_lock:
        _kernel = kernel


def get_runtime_kernel() -> RuntimeKernel:
    kernel = _kernel
    if kernel is None:
        raise RuntimeError("Runtime kernel not registered")
    return kernel


def try_get_runtime_kernel() -> RuntimeKernel | None:
    return _kernel


class kernel_override:
    def __init__(self, kernel: RuntimeKernel):
        self._kernel = kernel
        self._previous: RuntimeKernel | None = None

    def __enter__(self):
        global _kernel
        with _kernel_lock:
            self._previous = _kernel
            _kernel = self._kernel
        return self._kernel

    def __exit__(self, exc_type, exc, tb):
        global _kernel
        with _kernel_lock:
            _kernel = self._previous
        return False


__all__ = [
    "RuntimeTaskServices",
    "RuntimeActorServices",
    "RuntimeTopicServices",
    "RuntimeSourceServices",
    "RuntimeIOService",
    "RuntimeStateServices",
    "RuntimeFlowServices",
    "RuntimeClusterServices",
    "RuntimeNodeControlServices",
    "RuntimeKernel",
    "RuntimeKernelBuilder",
    "register_kernel",
    "get_runtime_kernel",
    "try_get_runtime_kernel",
    "kernel_override",
]
