"""
Structured Payload Support for Terminal Broker.

Lets terminals share rich data alongside text messages — file references, diffs,
test results, build statuses, and numeric metrics. Payloads are stored as an extra
``payload`` field in the JSONL message entry.

Payload types:
  file_ref     — Reference a file path with optional line range
  diff         — A code diff (before/after)
  test_result  — Test execution results
  build_status — Build/deploy status
  metric       — A numeric metric to track

Usage:
  from brynq.payloads import post_with_payload, format_payload, extract_payloads

  post_with_payload("fixes", "FIX-123 applied", "diff", {
      "file": "server.py", "before": "old", "after": "new", "description": "FIX-123"
  })

  msgs = extract_payloads(messages, payload_type="test_result")
  for m in msgs:
      print(format_payload(m["payload"]))
"""

import uuid
from datetime import datetime, timezone
from typing import Any, Optional

from .server import (
    CHANNELS_DIR,
    PLATFORM,
    SESSION_ID,
    SESSION_NAME,
    _append_jsonl_sync,
    _read_jsonl_sync,
    _update_heartbeat_sync,
)

# ── Payload Type Definitions ──────────────────────────────────────────────

PAYLOAD_TYPES = {
    "file_ref": {
        "required": ["path"],
        "optional": ["lines", "description"],
    },
    "diff": {
        "required": ["file", "before", "after"],
        "optional": ["description"],
    },
    "test_result": {
        "required": ["passed", "failed"],
        "optional": ["errors", "duration_s", "failures"],
    },
    "build_status": {
        "required": ["status"],
        "optional": ["url", "duration_s", "artifacts"],
    },
    "metric": {
        "required": ["name", "value"],
        "optional": ["unit", "trend"],
    },
}


# ── Validation ────────────────────────────────────────────────────────────


def _validate_payload(payload_type: str, payload_data: dict[str, Any]) -> dict[str, Any]:
    """Validate and build a payload dict from type + data.

    Raises ValueError if the payload_type is unknown or required fields are missing.
    Returns a complete payload dict with ``type`` included.
    """
    if payload_type not in PAYLOAD_TYPES:
        raise ValueError(
            f"Unknown payload type '{payload_type}'. "
            f"Valid types: {', '.join(sorted(PAYLOAD_TYPES))}"
        )

    schema = PAYLOAD_TYPES[payload_type]
    missing = [f for f in schema["required"] if f not in payload_data]
    if missing:
        raise ValueError(
            f"Payload type '{payload_type}' requires fields: {', '.join(missing)}"
        )

    # Build payload with type tag, required fields, then optional fields if present
    payload: dict[str, Any] = {"type": payload_type}
    for field in schema["required"]:
        payload[field] = payload_data[field]
    for field in schema["optional"]:
        if field in payload_data:
            payload[field] = payload_data[field]

    return payload


# ── Core Functions ────────────────────────────────────────────────────────


def post_with_payload(
    channel: str,
    message: str,
    payload_type: str,
    payload_data: dict[str, Any],
    msg_type: str = "info",
) -> dict[str, Any]:
    """Post a message with a structured payload attached.

    Args:
        channel: Channel name (e.g. "fixes", "general").
        message: Human-readable message text.
        payload_type: One of: file_ref, diff, test_result, build_status, metric.
        payload_data: Dict of payload fields (validated against type schema).
        msg_type: Message type tag (default "info").

    Returns:
        The complete message entry dict that was written.

    Raises:
        ValueError: If payload_type is unknown or required fields are missing.
    """
    _update_heartbeat_sync()

    payload = _validate_payload(payload_type, payload_data)

    entry = {
        "id": uuid.uuid4().hex[:12],
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": SESSION_ID,
        "session_name": SESSION_NAME,
        "platform": PLATFORM,
        "channel": channel,
        "msg_type": msg_type,
        "message": message,
        "payload": payload,
    }

    path = CHANNELS_DIR / f"{channel}.jsonl"
    _append_jsonl_sync(path, entry)

    return entry


def format_payload(payload: dict[str, Any]) -> str:
    """Render a payload dict into human-readable text for display.

    Args:
        payload: A payload dict (must contain a ``type`` field).

    Returns:
        Formatted multi-line string representation.
    """
    ptype = payload.get("type", "unknown")

    if ptype == "file_ref":
        path = payload.get("path", "?")
        lines = payload.get("lines", "")
        desc = payload.get("description", "")
        loc = f"{path}:{lines}" if lines else path
        return f"[FILE] {loc}" + (f"\n       {desc}" if desc else "")

    if ptype == "diff":
        file = payload.get("file", "?")
        desc = payload.get("description", "")
        before = payload.get("before", "")
        after = payload.get("after", "")
        header = f"[DIFF] {file}" + (f" -- {desc}" if desc else "")
        return f"{header}\n  - {before}\n  + {after}"

    if ptype == "test_result":
        passed = payload.get("passed", 0)
        failed = payload.get("failed", 0)
        errors = payload.get("errors", 0)
        duration = payload.get("duration_s")
        failures = payload.get("failures", [])
        total = passed + failed + errors
        time_str = f" in {duration:.1f}s" if duration is not None else ""
        line = f"[TEST] {passed}/{total} passed, {failed} failed, {errors} errors{time_str}"
        if failures:
            line += "\n  failures: " + ", ".join(failures)
        return line

    if ptype == "build_status":
        status = payload.get("status", "?")
        icon = "OK" if status == "success" else "FAIL"
        url = payload.get("url", "")
        duration = payload.get("duration_s")
        artifacts = payload.get("artifacts", [])
        time_str = f" in {duration:.1f}s" if duration is not None else ""
        line = f"[BUILD {icon}] {status}{time_str}"
        if url:
            line += f"\n  url: {url}"
        if artifacts:
            line += f"\n  artifacts: {', '.join(artifacts)}"
        return line

    if ptype == "metric":
        name = payload.get("name", "?")
        value = payload.get("value", "?")
        unit = payload.get("unit", "")
        trend = payload.get("trend", "")
        trend_arrow = {"up": " ^", "down": " v", "flat": " ="}.get(trend, "")
        unit_str = f" {unit}" if unit else ""
        return f"[METRIC] {name}: {value}{unit_str}{trend_arrow}"

    # Fallback for unknown payload types
    return f"[{ptype.upper()}] {payload}"


def extract_payloads(
    messages: list[dict[str, Any]],
    payload_type: Optional[str] = None,
) -> list[dict[str, Any]]:
    """Filter messages that have payloads, optionally by type.

    Args:
        messages: List of message dicts (as returned by ``_read_jsonl_sync``).
        payload_type: If set, only return messages whose payload matches this type.

    Returns:
        Filtered list of message dicts that contain a ``payload`` field.
    """
    results = []
    for msg in messages:
        payload = msg.get("payload")
        if payload is None:
            continue
        if payload_type is not None and payload.get("type") != payload_type:
            continue
        results.append(msg)
    return results
