"""
Wire serialization/deserialization for AMI SDK.

Handles conversion between SDK models and JSON wire format for API communication.
"""
import unicodedata
from typing import Dict, List, Any, Optional
from memrail.atoms import StateAtom, TagAtom, EventAtom
from memrail.errors import AMIBadRequest
from memrail.models import (
    SelectedItem,
    ActionToolCall,
    ActionDecisionPrompt,
    ActionRoute,
    ActionContextDirective,
    IdempotencyMeta,
    InvokeResponse,
)


def serialize_atom(atom: Any, project_id: Optional[str] = None) -> Dict[str, Any]:
    """
    Serialize an atom to wire format (JSON-compatible dict).

    Args:
        atom: StateAtom, TagAtom, or EventAtom instance
        project_id: Optional project ID to inject for EventAtoms (required for invoke context_atoms)

    Returns:
        Dictionary in wire format ready for JSON serialization
    """
    if isinstance(atom, StateAtom):
        return {
            "key": atom.key,
            "value": atom.value,
            "type": "state"
        }
    elif isinstance(atom, TagAtom):
        return {
            "kind": atom.kind,
            "value": atom.value,
            "type": "tag"
        }
    elif isinstance(atom, EventAtom):
        # Use the atom's own to_dict() method which handles the wire format
        data = atom.to_dict()
        # Inject project_id if provided (required for invoke context_atoms)
        if project_id:
            data["project_id"] = project_id
        return data
    else:
        raise AMIBadRequest(f"Unknown atom type: {type(atom).__name__}")


def serialize_atoms(atoms: List[Any], project_id: Optional[str] = None) -> List[Dict[str, Any]]:
    """
    Serialize a list of atoms to wire format.

    Args:
        atoms: List of StateAtom, TagAtom, or EventAtom instances
        project_id: Optional project ID to inject for EventAtoms (required for invoke context_atoms)

    Returns:
        List of dictionaries in wire format
    """
    return [serialize_atom(atom, project_id=project_id) for atom in atoms]


def serialize_event_for_ingestion(event: EventAtom) -> Dict[str, Any]:
    """
    Serialize EventAtom for event ingestion endpoint.

    Event ingestion uses a different format than context_atoms:
    - No "type" discriminator field
    - Direct event fields (subject, verb, object, ts, anchor, etc.)

    Args:
        event: EventAtom instance

    Returns:
        Dictionary in event ingestion wire format (without "type" field)
    """
    # Get base serialization from atom
    data = event.to_dict()

    # Remove "type" field for event ingestion
    data.pop("type", None)

    return data


def parse_event_topic(topic: str) -> Dict[str, str]:
    """
    Parse event topic into semantic components.

    Supports:
    - 3-token format: subject.verb.object
    - 4-token format: predicate.subject.verb.object

    Args:
        topic: Event topic string (e.g., "user.login.success")

    Returns:
        Dictionary with parsed components (subject, verb, object, optional predicate)

    Raises:
        AMIBadRequest: If topic format is invalid (not 3 or 4 tokens)
    """
    tokens = topic.split(".")

    if len(tokens) == 3:
        return {
            "subject": tokens[0],
            "verb": tokens[1],
            "object": tokens[2]
        }
    elif len(tokens) == 4:
        return {
            "predicate": tokens[0],
            "subject": tokens[1],
            "verb": tokens[2],
            "object": tokens[3]
        }
    else:
        raise AMIBadRequest(
            f"Event topic must have 3 or 4 tokens (got {len(tokens)}): {topic}"
        )


def normalize_topic(topic: str) -> str:
    """
    Normalize event topic using NFKC normalization and case folding.

    NFKC normalization ensures compatibility across different Unicode representations.
    Case folding handles locale-specific case conversions (e.g., ß → ss).

    Args:
        topic: Raw topic string

    Returns:
        Normalized topic string (lowercased, Unicode-normalized)
    """
    # Apply NFKC normalization (compatibility decomposition + canonical composition)
    normalized = unicodedata.normalize("NFKC", topic)

    # Apply case folding (more aggressive than .lower(), handles special Unicode cases)
    return normalized.casefold()


def deserialize_action(wire_data: Dict[str, Any]):
    """
    Deserialize action from wire format.

    NOTE: For Phase 3.1, action deserialization returns raw dict to handle
    API format differences. Full typed action parsing will be added in Phase 3.2+.

    The API uses different action structures than initially spec'd:
    - API tool_call: {type, intent, tool: {tool_id, version, args}}
    - Spec tool_call: {type, tool, name, args}

    Args:
        wire_data: Raw action data from API

    Returns:
        Raw action dictionary (Phase 3.1 - simplified)
        Future: Typed Action models with proper mapping
    """
    # Phase 3.1: Return raw dict to avoid mapping errors
    # Phase 3.2+: Add proper type-specific deserialization
    return wire_data.copy() if wire_data else None


def deserialize_selected_item(wire_data: Dict[str, Any]) -> SelectedItem:
    """
    Deserialize SelectedItem from API wire format to SDK model.

    The API uses "payload" field, SDK uses "action" field.

    Args:
        wire_data: Raw SelectedItem data from API

    Returns:
        SelectedItem model instance
    """
    # API uses "payload", SDK spec uses "action" - map accordingly
    action_data = wire_data.get("payload")
    action = deserialize_action(action_data) if action_data else None

    return SelectedItem(
        id=wire_data["id"],
        emu_key=wire_data["emu_key"],
        emu_version=wire_data.get("emu_version"),
        activation_id=wire_data.get("activation_id"),
        action=action,
        score=wire_data.get("score"),
        resolved_idempotency_key=wire_data.get("resolved_idempotency_key")
    )


def deserialize_invoke_response(wire_data: Dict[str, Any]) -> InvokeResponse:
    """
    Deserialize invoke response from API wire format to SDK model.

    Args:
        wire_data: Raw JSON response from /invoke endpoint

    Returns:
        Typed InvokeResponse model
    """
    # Deserialize idempotency metadata
    idempotency = None
    if wire_data.get("idempotency"):
        idempotency = IdempotencyMeta(
            applied=wire_data["idempotency"]["applied"],
            invoke_key=wire_data["idempotency"].get("invoke_key")
        )

    # Deserialize selected items
    selected = [
        deserialize_selected_item(item)
        for item in wire_data.get("selected", [])
    ]

    return InvokeResponse(
        invocation_id=wire_data["invocation_id"],
        idempotency=idempotency,
        selected=selected,
        trace=wire_data.get("trace")
    )
