from __future__ import annotations

from collections.abc import Mapping
from typing import Any, Callable
import uuid

from flownet.v1.client.handles import InstanceHandle, RegistrationHandle, ResourceKind
from flownet.v1.client.registries import SurfaceRegistry


_BINDING_KEYS = (
    "in_topic",
    "out_topic",
    "subscriptions",
    "publish_targets",
    "mode",
    "parallelism",
    "ctor_args",
    "ctor_kwargs",
)


class V1RuntimeClient:
    """
    v1 canonical client skeleton.

    Provides the frozen three-stage surface:
    declaration -> registration -> instantiation.
    """

    def __init__(
        self,
        *,
        owner: str = "local-owner",
        id_factory: Callable[[], str] | None = None,
    ) -> None:
        self._id_factory = id_factory or (lambda: uuid.uuid4().hex)
        self.flows = _ClientSurface(
            kind="flow",
            registry=SurfaceRegistry(kind="flow", owner=owner, id_factory=self._id_factory),
            id_factory=self._id_factory,
        )
        self.producers = _ClientSurface(
            kind="producer",
            registry=SurfaceRegistry(kind="producer", owner=owner, id_factory=self._id_factory),
            id_factory=self._id_factory,
        )
        self.processes = _ClientSurface(
            kind="process",
            registry=SurfaceRegistry(kind="process", owner=owner, id_factory=self._id_factory),
            id_factory=self._id_factory,
        )
        self.actors = _ClientSurface(
            kind="actor",
            registry=SurfaceRegistry(kind="actor", owner=owner, id_factory=self._id_factory),
            id_factory=self._id_factory,
        )
        self.stateless = _ClientSurface(
            kind="stateless",
            registry=SurfaceRegistry(kind="stateless", owner=owner, id_factory=self._id_factory),
            id_factory=self._id_factory,
        )


class _ClientSurface:
    def __init__(
        self,
        *,
        kind: ResourceKind,
        registry: SurfaceRegistry,
        id_factory: Callable[[], str],
    ) -> None:
        self._kind = kind
        self._registry = registry
        self._id_factory = id_factory

    def register(
        self,
        declaration: Any,
        *,
        uri: str | None = None,
        metadata: Mapping[str, Any] | None = None,
    ) -> RegistrationHandle:
        return self._registry.register(
            declaration,
            uri=uri,
            metadata=metadata,
        )

    def instantiate(
        self,
        declaration_or_registration: Any,
        *,
        config: Mapping[str, Any] | None = None,
        policies: Mapping[str, Any] | None = None,
        **kwargs: Any,
    ) -> InstanceHandle:
        registration, implicit_registration = self._registry.ensure_registration(
            declaration_or_registration,
        )
        bindings = _pick_bindings(kwargs)
        options = dict(kwargs)

        return InstanceHandle(
            kind=self._kind,
            instance_id=_normalize_non_empty(self._id_factory(), field_name="instance_id"),
            registration=registration,
            implicit_registration=implicit_registration,
            config=_normalize_mapping(config, field_name="config"),
            policies=_normalize_mapping(policies, field_name="policies"),
            bindings=bindings,
            options=options,
        )

    def get_discoverable(self, uri: str) -> RegistrationHandle | None:
        return self._registry.get_discoverable(uri)

    def list_discoverable(self) -> list[RegistrationHandle]:
        return self._registry.list_discoverable()

    def list_registrations(self) -> list[RegistrationHandle]:
        return self._registry.list_registrations()


def _pick_bindings(raw_options: dict[str, Any]) -> dict[str, Any]:
    bindings: dict[str, Any] = {}
    for key in _BINDING_KEYS:
        if key in raw_options:
            bindings[key] = raw_options.pop(key)
    return bindings


def _normalize_mapping(
    raw_value: Mapping[str, Any] | None,
    *,
    field_name: str,
) -> dict[str, Any]:
    if raw_value is None:
        return {}
    if not isinstance(raw_value, Mapping):
        raise TypeError(f"{field_name} must be a mapping when provided.")
    return dict(raw_value)


def build_runtime_client(
    *,
    owner: str = "local-owner",
) -> V1RuntimeClient:
    return V1RuntimeClient(owner=owner)


def _normalize_non_empty(raw_value: Any, *, field_name: str) -> str:
    normalized = str(raw_value or "").strip()
    if not normalized:
        raise ValueError(f"{field_name} must be non-empty.")
    return normalized


__all__ = ["V1RuntimeClient", "build_runtime_client"]
