from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from collections.abc import Mapping
from typing import Any, Literal, Optional, Callable, Union
from flownet.compiler.flowtask import FlowTask
from flownet.compiler.transformation import Transformation
from flownet.api.actor_method_ref import ActorMethodRef
from flownet.core.types import PayloadData
from flownet.core.service_event import ServiceEvent
# from flownet.topology.fork_controller import ForkLog

# TODO: 在任务包里是否append task，可以做成runtime policy
# full/ top_only / downstream_once / lazy_pull
# ARCH_NOTE:
# - FlowRequest currently carries live TransformationFrame objects.
# - Remote forwarding therefore serializes executable frame topology together
#   with payload (via transport-level cloudpickle).
# - This is run-oriented and in-memory friendly. A future persistent flow
#   catalog can move toward logical IDs + runtime resolution.
# - Terminology freeze (WS-0 / M0): FlowRequest is the legacy runtime class
#   name for the execution token. New design docs call this semantic object
#   "ExecRequest" while keeping this class for compatibility.

class BaseFlowFrame(ABC):
    pass

class TransformationFrame(BaseFlowFrame):
    def __init__(
        self,
        transformation: Transformation,
        payload: PayloadData,
        meta: dict | None = None,
    ):
        self.transformation: Transformation = transformation
        self.payload: PayloadData = payload
        self.meta: dict | None = meta

class ActorMethodFrame(BaseFlowFrame):
    def __init__(self, rmf: ActorMethodRef, payload: PayloadData, tap:bool = False):
        self.rmf: ActorMethodRef = rmf
        self.payload: PayloadData = payload


class FlowStack:
    def __init__(self, frames:Union[BaseFlowFrame, list[BaseFlowFrame]]):  
        if isinstance(frames, list):
            self.stack: list[BaseFlowFrame] = frames
        else:
            self.stack: list[BaseFlowFrame] = [frames]

    def push(self, frame: BaseFlowFrame):
        self.stack.append(frame)
        
    def pop(self) -> BaseFlowFrame:
        return self.stack.pop()
    
    def top(self) -> BaseFlowFrame:
        if self.empty():
            return None
        return self.stack[-1]
    
    def pop_sink_transformations(self):
        # Backward-compatible alias: sink used to mean return boundary.
        self.pop_return_transformations()

    def pop_return_transformations(self):
        top_frame = self.top()
        while(
            isinstance(top_frame, TransformationFrame)
            and top_frame.transformation.is_return == True
        ):
            self.pop()
            top_frame = self.top()
            
            
    
    def empty(self) -> bool:
        return len(self.stack) == 0


@dataclass(frozen=True, init=False)
class FlowAckRoute:
    coordinator_target: str
    coordinator_address: str | None = None

    def __init__(
        self,
        coordinator_target: str | None = None,
        *,
        actor_id: str | None = None,
        coordinator_actor_id: str | None = None,
        coordinator_address: str | None = None,
    ) -> None:
        normalized_target = str(
            coordinator_target
            or coordinator_actor_id
            or actor_id
            or ""
        ).strip()
        if not normalized_target:
            raise ValueError("FlowAckRoute coordinator target must be a non-empty string.")
        normalized_coordinator_address = str(coordinator_address or "").strip() or None
        object.__setattr__(self, "coordinator_target", normalized_target)
        object.__setattr__(self, "coordinator_address", normalized_coordinator_address)

    @property
    def coordinator_actor_id(self) -> str:
        # Canonical naming bridge:
        # coordinator_actor_id is the semantic target; actor_id remains as
        # migration alias until actor-centric naming is fully retired.
        return self.coordinator_target

    @property
    def actor_id(self) -> str:
        # Backward-compat field alias.
        return self.coordinator_target


STREAM_TRACKER_ACK_COORDINATOR_ACTOR_ID = "__flownet_stream_tracker__"


def is_stream_tracker_ack_coordinator_target(value: str | None) -> bool:
    normalized = str(value or "").strip()
    if not normalized:
        return False
    return normalized == STREAM_TRACKER_ACK_COORDINATOR_ACTOR_ID


def is_stream_tracker_ack_coordinator_actor_id(value: str | None) -> bool:
    # Backward-compatible alias for actor-centric naming.
    return is_stream_tracker_ack_coordinator_target(value)


def build_flow_ack_route(
    actor_id: str | None = None,
    *,
    coordinator_target: str | None = None,
    coordinator_actor_id: str | None = None,
    coordinator_address: str | None = None,
) -> FlowAckRoute | None:
    normalized_target = str(
        coordinator_target
        or coordinator_actor_id
        or actor_id
        or ""
    ).strip()
    if not normalized_target:
        return None
    normalized_coordinator_address = str(coordinator_address or "").strip() or None
    return FlowAckRoute(
        coordinator_target=normalized_target,
        coordinator_address=normalized_coordinator_address,
    )


def coerce_flow_ack_route(
    value: FlowAckRoute | Mapping[str, Any] | str | None,
) -> FlowAckRoute | None:
    if value is None:
        return None
    if isinstance(value, FlowAckRoute):
        return build_flow_ack_route(
            coordinator_target=value.coordinator_target,
            coordinator_address=value.coordinator_address,
        )
    if isinstance(value, Mapping):
        coordinator_kind = str(
            value.get("coordinator_kind")
            or value.get("target_kind")
            or ""
        ).strip().lower()
        coordinator_target = value.get("coordinator_target")
        if coordinator_target is None and coordinator_kind in {
            "stream_tracker",
            "request_tracker",
            "tracker",
        }:
            coordinator_target = STREAM_TRACKER_ACK_COORDINATOR_ACTOR_ID
        coordinator_address = value.get("coordinator_address")
        if coordinator_target is not None:
            return build_flow_ack_route(
                coordinator_target=coordinator_target,
                coordinator_address=(
                    str(coordinator_address or "").strip() or None
                ),
            )
        for nested_key in ("ack_route", "route", "target"):
            nested = value.get(nested_key)
            if nested is None:
                continue
            resolved_nested = coerce_flow_ack_route(nested)
            if resolved_nested is not None:
                normalized_coordinator_address = str(coordinator_address or "").strip() or None
                if normalized_coordinator_address and not resolved_nested.coordinator_address:
                    return build_flow_ack_route(
                        coordinator_target=resolved_nested.coordinator_target,
                        coordinator_address=normalized_coordinator_address,
                    )
                return resolved_nested
        return None
    if isinstance(value, str):
        return build_flow_ack_route(coordinator_target=value)
    return None


def resolve_flow_ack_coordinator_target(
    value: FlowAckRoute | Mapping[str, Any] | str | None,
) -> str | None:
    route = coerce_flow_ack_route(value)
    if route is None:
        return None
    return route.coordinator_target


def resolve_flow_ack_target(
    value: FlowAckRoute | Mapping[str, Any] | str | None,
) -> str | None:
    # Backward-compatible alias with canonical target naming.
    return resolve_flow_ack_coordinator_target(value)


def resolve_flow_ack_coordinator_address(
    value: FlowAckRoute | Mapping[str, Any] | str | None,
) -> str | None:
    route = coerce_flow_ack_route(value)
    if route is None:
        return None
    return route.coordinator_address


@dataclass
class FlowRequest:
    """
    Legacy runtime execution token.

    Design-freeze naming:
    - External request handle: RequestRef (opaque to runtime internals)
    - Internal execution token: ExecRequest (compat alias of FlowRequest)
    """
    flow_stack: FlowStack
    request_id: str
    origin_address: str
    ack_route: FlowAckRoute | None = None

    @property
    def request_ref_id(self) -> str:
        """Compatibility alias of request_id for request-ref terminology."""
        return self.request_id
    
    
@dataclass
class ActorCall:
    source: str
    destination: ActorMethodRef
    payload: PayloadData


@dataclass
class FlownetPacket:
    type: Literal[
        "flowrequest",
        "actorcall",
        "response",
        "service_event",
        "topic_subscribe",
        "topic_unsubscribe",
        "topic_list",
        "topic_event",
        "topic_claim_producer",
        "topic_release_producer",
        "node_control",
        "cv.delta.push",
        "cv.delta.pull",
        "cv.delta.ack",
        "cv.snapshot",
    ]
    uuid: str
    reply:bool
    packet_content: Any


# Compat alias used by refactor documents and phased migration code.
# NOTE: keep class name FlowRequest stable in this phase to avoid behavioral
# changes and broad import churn.
ExecRequest = FlowRequest


__all__ = [
    "BaseFlowFrame",
    "TransformationFrame",
    "ActorMethodFrame",
    "FlowStack",
    "FlowAckRoute",
    "build_flow_ack_route",
    "coerce_flow_ack_route",
    "is_stream_tracker_ack_coordinator_target",
    "is_stream_tracker_ack_coordinator_actor_id",
    "STREAM_TRACKER_ACK_COORDINATOR_ACTOR_ID",
    "resolve_flow_ack_coordinator_target",
    "resolve_flow_ack_target",
    "resolve_flow_ack_coordinator_address",
    "FlowRequest",
    "ExecRequest",
    "ActorCall",
    "FlownetPacket",
]
