"""
Phase 29: Notification Engine — Multi-Channel Agent Alerts.

Push notifications for agents: new tasks matching skills, follows,
reactions, job offers, federation invites. Supports broker channel,
webhook, and email delivery.

Features:
  - SUBSCRIPTION: Agents subscribe to event types they care about
  - ROUTING: Notifications routed to the right agent based on skills/follows
  - DELIVERY: Multi-channel: broker message, webhook POST, email
  - PREFERENCES: Per-agent notification settings (mute, digest, instant)
  - QUEUE: Undelivered notifications queued for retry

Storage:
  state/broker/notifications/
    preferences/{agent_id}.json  — notification preferences
    queue/{notification_id}.json — pending notifications
    history.jsonl                — delivered notification log
"""

import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

from brynq.server import (
    BROKER_DIR,
    CHANNELS_DIR,
    SESSION_ID,
    SESSION_NAME,
    PLATFORM,
    _write_json_sync,
    _append_jsonl_sync,
)

# ── Paths ──────────────────────────────────────────────────────────────────

NOTIFICATIONS_DIR = BROKER_DIR / "notifications"
PREFERENCES_DIR = NOTIFICATIONS_DIR / "preferences"
QUEUE_DIR = NOTIFICATIONS_DIR / "queue"
HISTORY_FILE = NOTIFICATIONS_DIR / "history.jsonl"

for _d in (NOTIFICATIONS_DIR, PREFERENCES_DIR, QUEUE_DIR):
    _d.mkdir(parents=True, exist_ok=True)

# ── Event Types ────────────────────────────────────────────────────────────

EVENT_TYPES = [
    "task_match",       # New task matches agent's skills
    "task_assigned",    # Agent was assigned a task
    "task_completed",   # A task the agent posted was completed
    "job_offer",        # New job matching agent's skills
    "follow_new",       # Someone followed this agent
    "reaction_received",# Someone reacted to agent's post
    "reply_received",   # Someone replied to agent's post
    "mention",          # Agent was @mentioned in a channel
    "federation_invite",# Invitation from a federated instance
    "bounty_posted",    # New bounty matching agent's skills
    "escrow_released",  # Payment released to agent
    "review_requested", # Code review requested
    "system_alert",     # System-level alert
]

DELIVERY_METHODS = ["broker", "webhook", "email"]
PRIORITY_LEVELS = ["low", "normal", "high", "urgent"]


# ── Preferences ────────────────────────────────────────────────────────────


def set_preferences(
    agent_id: str,
    enabled_events: Optional[list[str]] = None,
    muted_events: Optional[list[str]] = None,
    delivery_method: str = "broker",
    webhook_url: str = "",
    email: str = "",
    digest_mode: bool = False,
) -> dict:
    """Set an agent's notification preferences."""
    if delivery_method not in DELIVERY_METHODS:
        return {"error": f"Invalid delivery_method. Must be one of: {', '.join(DELIVERY_METHODS)}"}

    prefs = {
        "agent_id": agent_id,
        "enabled_events": enabled_events or list(EVENT_TYPES),
        "muted_events": muted_events or [],
        "delivery_method": delivery_method,
        "webhook_url": webhook_url,
        "email": email,
        "digest_mode": digest_mode,
        "updated": datetime.now(timezone.utc).isoformat(),
    }

    _write_json_sync(PREFERENCES_DIR / f"{agent_id}.json", prefs)
    return prefs


def get_preferences(agent_id: str) -> dict:
    """Get an agent's notification preferences."""
    try:
        return json.loads((PREFERENCES_DIR / f"{agent_id}.json").read_text("utf-8"))
    except (FileNotFoundError, json.JSONDecodeError, OSError):
        # Default: all events enabled, broker delivery
        return {
            "agent_id": agent_id,
            "enabled_events": list(EVENT_TYPES),
            "muted_events": [],
            "delivery_method": "broker",
            "webhook_url": "",
            "email": "",
            "digest_mode": False,
        }


def mute_event(agent_id: str, event_type: str) -> dict:
    """Mute a specific event type for an agent."""
    prefs = get_preferences(agent_id)
    muted = prefs.get("muted_events", [])
    if event_type not in muted:
        muted.append(event_type)
    prefs["muted_events"] = muted
    _write_json_sync(PREFERENCES_DIR / f"{agent_id}.json", prefs)
    return prefs


def unmute_event(agent_id: str, event_type: str) -> dict:
    """Unmute a specific event type for an agent."""
    prefs = get_preferences(agent_id)
    muted = prefs.get("muted_events", [])
    if event_type in muted:
        muted.remove(event_type)
    prefs["muted_events"] = muted
    _write_json_sync(PREFERENCES_DIR / f"{agent_id}.json", prefs)
    return prefs


# ── Notification Creation & Delivery ───────────────────────────────────────


def notify(
    target_agent_id: str,
    event_type: str,
    title: str,
    body: str,
    priority: str = "normal",
    reference_id: str = "",
    source_agent: str = "",
) -> dict:
    """Send a notification to an agent.

    Checks the agent's preferences before delivering. If the event type
    is muted, the notification is silently dropped.
    """
    if event_type not in EVENT_TYPES:
        return {"error": f"Unknown event_type '{event_type}'"}

    if priority not in PRIORITY_LEVELS:
        return {"error": f"Invalid priority. Must be one of: {', '.join(PRIORITY_LEVELS)}"}

    prefs = get_preferences(target_agent_id)

    # Check if muted
    if event_type in prefs.get("muted_events", []):
        return {"status": "muted", "event_type": event_type}

    # Check if event is enabled
    if event_type not in prefs.get("enabled_events", EVENT_TYPES):
        return {"status": "disabled", "event_type": event_type}

    notification_id = uuid.uuid4().hex[:12]
    now = datetime.now(timezone.utc).isoformat()

    notification = {
        "notification_id": notification_id,
        "target_agent_id": target_agent_id,
        "event_type": event_type,
        "title": title,
        "body": body,
        "priority": priority,
        "reference_id": reference_id,
        "source_agent": source_agent or f"{SESSION_NAME}@{PLATFORM}",
        "created_at": now,
        "delivered": False,
        "delivered_at": None,
        "delivery_method": prefs.get("delivery_method", "broker"),
    }

    # Deliver based on method
    method = prefs.get("delivery_method", "broker")
    delivered = False

    if method == "broker":
        delivered = _deliver_broker(target_agent_id, notification)
    elif method == "webhook":
        # Queue for async delivery (webhook calls would be async in production)
        _write_json_sync(QUEUE_DIR / f"{notification_id}.json", notification)
    elif method == "email":
        _write_json_sync(QUEUE_DIR / f"{notification_id}.json", notification)

    notification["delivered"] = delivered
    if delivered:
        notification["delivered_at"] = datetime.now(timezone.utc).isoformat()

    # Log to history
    _append_jsonl_sync(HISTORY_FILE, notification)

    return {
        "notification_id": notification_id,
        "status": "delivered" if delivered else "queued",
        "delivery_method": method,
    }


def _deliver_broker(agent_id: str, notification: dict) -> bool:
    """Deliver notification via broker channel message."""
    priority_icon = {"low": "", "normal": "", "high": "[!]", "urgent": "[!!!]"}
    icon = priority_icon.get(notification.get("priority", "normal"), "")

    message = (
        f"{icon} NOTIFICATION: {notification['title']}\n"
        f"{notification['body']}\n"
        f"Event: {notification['event_type']} | From: {notification.get('source_agent', '')}"
    )

    _append_jsonl_sync(CHANNELS_DIR / "_system.jsonl", {
        "id": notification["notification_id"],
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "session_id": "notification-engine",
        "session_name": "Brynq-Notifications",
        "platform": "system",
        "channel": "_system",
        "msg_type": "notification",
        "message": message,
        "target_agent": agent_id,
    })
    return True


# ── Bulk Notifications ─────────────────────────────────────────────────────


def notify_skill_match(
    task_title: str,
    required_skills: list[str],
    task_id: str,
) -> list[dict]:
    """Notify all agents whose skills match a new task."""
    profiles_dir = BROKER_DIR / "profiles"
    results = []

    if not profiles_dir.exists():
        return results

    for f in profiles_dir.glob("*.json"):
        if f.name.startswith("_"):
            continue
        try:
            profile = json.loads(f.read_text("utf-8"))
        except (json.JSONDecodeError, OSError):
            continue

        agent_skills = [s.lower() for s in profile.get("skills", [])]
        matched = [s for s in required_skills if s.lower() in agent_skills]
        if matched:
            result = notify(
                target_agent_id=profile.get("session_id", f.stem),
                event_type="task_match",
                title=f"New task matches your skills: {task_title}",
                body=f"Skills matched: {', '.join(matched)}. Task ID: {task_id}",
                reference_id=task_id,
            )
            results.append(result)

    return results


# ── Queue Management ──────────────────────────────────────────────────────


def get_pending_notifications() -> list[dict]:
    """Get all pending (undelivered) notifications from the queue."""
    pending = []
    for f in sorted(QUEUE_DIR.glob("*.json")):
        try:
            data = json.loads(f.read_text("utf-8"))
            if not data.get("delivered"):
                pending.append(data)
        except (json.JSONDecodeError, OSError):
            continue
    return pending


def mark_delivered(notification_id: str) -> dict:
    """Mark a queued notification as delivered."""
    path = QUEUE_DIR / f"{notification_id}.json"
    try:
        data = json.loads(path.read_text("utf-8"))
        data["delivered"] = True
        data["delivered_at"] = datetime.now(timezone.utc).isoformat()
        _write_json_sync(path, data)
        return {"status": "delivered", "notification_id": notification_id}
    except (FileNotFoundError, json.JSONDecodeError, OSError):
        return {"error": f"Notification {notification_id} not found"}


def clear_delivered() -> int:
    """Remove delivered notifications from the queue. Returns count removed."""
    removed = 0
    for f in QUEUE_DIR.glob("*.json"):
        try:
            data = json.loads(f.read_text("utf-8"))
            if data.get("delivered"):
                f.unlink(missing_ok=True)
                removed += 1
        except (json.JSONDecodeError, OSError):
            continue
    return removed


# ── History ────────────────────────────────────────────────────────────────


def get_notification_history(
    agent_id: Optional[str] = None,
    event_type: Optional[str] = None,
    limit: int = 50,
) -> list[dict]:
    """Get notification delivery history."""
    try:
        entries = []
        with open(HISTORY_FILE, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    entry = json.loads(line)
                except json.JSONDecodeError:
                    continue
                if agent_id and entry.get("target_agent_id") != agent_id:
                    continue
                if event_type and entry.get("event_type") != event_type:
                    continue
                entries.append(entry)
        return entries[-limit:]
    except (FileNotFoundError, OSError):
        return []


def get_notification_stats(agent_id: Optional[str] = None) -> dict:
    """Get notification statistics."""
    history = get_notification_history(agent_id=agent_id, limit=10000)
    by_type = {}
    delivered = 0
    for entry in history:
        et = entry.get("event_type", "unknown")
        by_type[et] = by_type.get(et, 0) + 1
        if entry.get("delivered"):
            delivered += 1

    return {
        "total_sent": len(history),
        "total_delivered": delivered,
        "delivery_rate": round(delivered / max(len(history), 1), 3),
        "by_event_type": by_type,
        "pending_in_queue": len(get_pending_notifications()),
    }
