from __future__ import annotations
from dataclasses import dataclass
from os import PathLike
from pathlib import Path
from typing import Any, TypeAlias
from collections.abc import Mapping

import yaml


@dataclass
class FlownetConfig:
    host: str = "127.0.0.1"
    port: int = 8780
    node_id: str = "default-node"
    threads: int = 8
    transport_mode: str = "auto"
    local_ipc_dir: str = "/tmp/flownet"
    transport_shm_threshold_bytes: int = 262144
    transport_shm_max_payload_bytes: int = 67108864
    transport_backend_cooldown_seconds: float = 1.0
    transport_backend_cooldown_max_entries: int = 4096
    transport_backend_cooldown_cleanup_interval: int = 128
    host_id: str | None = None
    uds_path: str | None = None
    shm_path: str | None = None
    transport_caps: tuple[str, ...] | None = None
    state_backend: str = "memory"
    state_dir: str | None = None
    state_sqlite_path: str | None = None
    state_compatibility_version: int = 1
    schedulable: bool = True


@dataclass
class NodeRuntimeConfig(FlownetConfig):
    role: str = "node"


@dataclass
class ContainerRuntimeConfig(FlownetConfig):
    role: str = "container"


RuntimeConfigInput: TypeAlias = FlownetConfig | Mapping[str, Any] | str | PathLike[str]
_YAML_SUFFIXES = {".yaml", ".yml"}
_CONFIG_FIELD_NAMES = set(FlownetConfig.__dataclass_fields__.keys())


def _select_config_mapping(
    raw: Mapping[str, Any],
    *,
    section: str | None = None,
) -> Mapping[str, Any]:
    # Prefer direct config keys when present.
    if any(key in raw for key in _CONFIG_FIELD_NAMES):
        return raw

    if section:
        scoped = raw.get(section)
        if isinstance(scoped, Mapping):
            return scoped

    runtime_scoped = raw.get("runtime")
    if isinstance(runtime_scoped, Mapping):
        return runtime_scoped

    return raw


def _mapping_to_flownet_config(
    mapping: Mapping[str, Any],
    *,
    section: str | None = None,
) -> FlownetConfig:
    resolved = dict(_select_config_mapping(mapping, section=section))
    transport_caps = resolved.get("transport_caps")
    if isinstance(transport_caps, list):
        resolved["transport_caps"] = tuple(str(item) for item in transport_caps)
    try:
        return FlownetConfig(**resolved)
    except TypeError as exc:
        raise ValueError(f"Invalid FlowNet config keys: {exc}") from exc


def _load_yaml_mapping_from_text(text: str) -> Mapping[str, Any]:
    loaded = yaml.safe_load(text)
    if loaded is None:
        raise ValueError("YAML config is empty.")
    if not isinstance(loaded, Mapping):
        raise ValueError("YAML config must deserialize to a mapping/object.")
    return loaded


def _load_yaml_mapping_from_path(path: Path) -> Mapping[str, Any]:
    if not path.exists():
        raise FileNotFoundError(f"FlowNet config file not found: {path}")
    return _load_yaml_mapping_from_text(path.read_text(encoding="utf-8"))


def _looks_like_yaml_path(value: str) -> bool:
    candidate = Path(value).expanduser()
    return candidate.exists() or candidate.suffix.lower() in _YAML_SUFFIXES


def coerce_flownet_config(
    config: RuntimeConfigInput | None,
    *,
    section: str | None = None,
) -> FlownetConfig:
    if config is None:
        return FlownetConfig()
    if isinstance(config, FlownetConfig):
        return config
    if isinstance(config, Mapping):
        return _mapping_to_flownet_config(config, section=section)
    if isinstance(config, PathLike):
        mapping = _load_yaml_mapping_from_path(Path(config).expanduser())
        return _mapping_to_flownet_config(mapping, section=section)
    if isinstance(config, str):
        stripped = config.strip()
        if not stripped:
            raise ValueError("FlowNet config string is empty.")
        if _looks_like_yaml_path(stripped):
            mapping = _load_yaml_mapping_from_path(Path(stripped).expanduser())
        else:
            mapping = _load_yaml_mapping_from_text(stripped)
        return _mapping_to_flownet_config(mapping, section=section)
    raise TypeError(
        "Unsupported FlowNet config input type: "
        f"{type(config)}; expected FlownetConfig, mapping, YAML text, or YAML path."
    )


__all__ = [
    "FlownetConfig",
    "NodeRuntimeConfig",
    "ContainerRuntimeConfig",
    "RuntimeConfigInput",
    "coerce_flownet_config",
]
