"""
Enterprise dashboard API handlers for Brynq.

Provides route handlers for CRM, ERP, Analytics, Marketplace, and
Onboarding modules.  These are designed to be mixed into the
DashboardHandler class — each handler accepts a DashboardHandler
instance and query params dict.

Usage from dashboard.py:
    from brynq.dashboard_enterprise import register_enterprise_routes
    # Inside do_GET, before the 404:
    if register_enterprise_routes(self, path, params):
        return
"""

import json
from datetime import datetime, timedelta, timezone
from pathlib import Path


# ── Helpers ───────────────────────────────────────────────────────────────


def _get_all_broker_dirs() -> list[dict]:
    """Import and delegate to the canonical broker discovery function."""
    from brynq.server import get_registered_brokers, BROKER_DIR

    brokers = get_registered_brokers()
    if not brokers:
        return [{"project": "local", "broker_dir": str(BROKER_DIR)}]
    return brokers


def _read_json_file(path: Path) -> dict | None:
    """Read a single JSON file.  Returns None on any failure."""
    if not path.exists():
        return None
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return None


def _read_jsonl_file(path: Path) -> list[dict]:
    """Read a JSONL file and return list of dicts, skipping bad lines."""
    if not path.exists():
        return []
    entries: list[dict] = []
    try:
        for line in path.read_text("utf-8").strip().splitlines():
            try:
                entries.append(json.loads(line))
            except json.JSONDecodeError:
                continue
    except OSError:
        pass
    return entries


# ── Route handlers ────────────────────────────────────────────────────────
#
# Each handler follows the same signature:
#   handler_instance  — the DashboardHandler (has ._send_json())
#   params            — parsed query string dict from parse_qs()


def _handle_relationships(handler, params: dict) -> None:
    """GET /api/relationships — list CRM relationships for all agents."""
    relationships: list[dict] = []

    for broker in _get_all_broker_dirs():
        interactions_dir = Path(broker["broker_dir"]) / "crm" / "interactions"
        if not interactions_dir.exists():
            continue
        proj = broker.get("project", "unknown")

        for f in sorted(interactions_dir.glob("*.json")):
            if f.name.startswith("_"):
                continue
            try:
                data = json.loads(f.read_text("utf-8"))
                data["project"] = proj
                relationships.append(data)
            except (json.JSONDecodeError, OSError):
                continue

    relationships.sort(
        key=lambda r: r.get("interaction_count", 0), reverse=True
    )
    handler._send_json(relationships)


def _handle_utilization(handler, params: dict) -> None:
    """GET /api/utilization — list ERP utilization data for all agents."""
    agents: list[dict] = []

    for broker in _get_all_broker_dirs():
        agents_dir = Path(broker["broker_dir"]) / "erp" / "agents"
        if not agents_dir.exists():
            continue
        proj = broker.get("project", "unknown")

        for f in sorted(agents_dir.glob("*.json")):
            try:
                profile = json.loads(f.read_text("utf-8"))
            except (json.JSONDecodeError, OSError):
                continue

            sid = profile.get("session_id", f.stem)
            active_count = len(profile.get("active_tasks", {}))
            max_tasks = profile.get("max_concurrent_tasks", 5)
            utilization = round(
                (active_count / max(max_tasks, 1)) * 100, 1
            )

            if utilization >= 90:
                status = "overloaded"
            elif utilization >= 60:
                status = "busy"
            elif utilization > 0:
                status = "active"
            else:
                status = "idle"

            agents.append({
                "session_id": sid,
                "project": proj,
                "active_task_count": active_count,
                "max_concurrent_tasks": max_tasks,
                "utilization_pct": utilization,
                "status": status,
                "token_budget_per_hour": profile.get(
                    "token_budget_per_hour", 0
                ),
                "total_tasks_completed": profile.get(
                    "total_tasks_completed", 0
                ),
                "total_cost_usd": profile.get("total_cost_usd", 0.0),
            })

    agents.sort(key=lambda a: a["utilization_pct"], reverse=True)
    handler._send_json(agents)


def _handle_leaderboard(handler, params: dict) -> None:
    """GET /api/leaderboard — analytics leaderboard across all brokers.

    Query params:
        metric  — one of tasks_completed, messages_sent (default: tasks_completed)
        limit   — max entries to return (default: 20)
    """
    metric = params.get("metric", ["tasks_completed"])[0]
    limit = int(params.get("limit", ["20"])[0])

    agent_scores: dict[str, dict] = {}

    for broker in _get_all_broker_dirs():
        broker_dir = Path(broker["broker_dir"])
        proj = broker.get("project", "unknown")

        # --- Aggregate task-based metrics from tasks directory ---
        tasks_dir = broker_dir / "tasks"
        if tasks_dir.exists():
            for f in sorted(tasks_dir.glob("*.json")):
                try:
                    task = json.loads(f.read_text("utf-8"))
                except (json.JSONDecodeError, OSError):
                    continue
                agent = task.get("assigned_to") or task.get(
                    "session_id", "unknown"
                )
                if agent not in agent_scores:
                    agent_scores[agent] = {
                        "agent": agent,
                        "project": proj,
                        "tasks_completed": 0,
                        "tasks_total": 0,
                        "messages_sent": 0,
                    }
                agent_scores[agent]["tasks_total"] += 1
                if task.get("status") == "completed":
                    agent_scores[agent]["tasks_completed"] += 1

        # --- Aggregate message counts from channel logs ---
        channels_dir = broker_dir / "channels"
        if channels_dir.exists():
            for f in sorted(channels_dir.glob("*.jsonl")):
                for entry in _read_jsonl_file(f):
                    agent = entry.get("session_id") or entry.get(
                        "session_name", "unknown"
                    )
                    if agent not in agent_scores:
                        agent_scores[agent] = {
                            "agent": agent,
                            "project": proj,
                            "tasks_completed": 0,
                            "tasks_total": 0,
                            "messages_sent": 0,
                        }
                    agent_scores[agent]["messages_sent"] += 1

    board = list(agent_scores.values())

    if metric == "messages_sent":
        board.sort(key=lambda x: -x.get("messages_sent", 0))
    else:
        # Default: tasks_completed
        board.sort(key=lambda x: -x.get("tasks_completed", 0))

    for rank, entry in enumerate(board[:limit], 1):
        entry["rank"] = rank

    handler._send_json(board[:limit])


def _handle_system_health(handler, params: dict) -> None:
    """GET /api/system-health — system health metrics across all brokers."""
    now = datetime.now(timezone.utc)
    total_active_agents = 0
    total_channels = 0
    total_messages_24h = 0
    pending_tasks = 0
    completed_tasks = 0
    failed_tasks = 0
    total_tasks = 0
    active_sessions_list: list[dict] = []

    for broker in _get_all_broker_dirs():
        broker_dir = Path(broker["broker_dir"])
        proj = broker.get("project", "unknown")

        # Sessions — count active (heartbeat < 10 min)
        sessions_dir = broker_dir / "sessions"
        if sessions_dir.exists():
            for f in sorted(sessions_dir.glob("*.json")):
                try:
                    data = json.loads(f.read_text("utf-8"))
                except (json.JSONDecodeError, OSError):
                    continue
                hb = data.get("heartbeat", "")
                try:
                    hb_dt = datetime.fromisoformat(hb)
                    if (now - hb_dt).total_seconds() < 600:
                        total_active_agents += 1
                        active_sessions_list.append({
                            "session_id": data.get("session_id"),
                            "name": data.get("name"),
                            "platform": data.get("platform"),
                            "project": proj,
                        })
                except (ValueError, TypeError):
                    pass

        # Channels — count files and recent messages
        channels_dir = broker_dir / "channels"
        if channels_dir.exists():
            cutoff_24h = (now - timedelta(hours=24)).isoformat()
            for f in channels_dir.glob("*.jsonl"):
                if f.stem.startswith("_"):
                    continue
                total_channels += 1
                for entry in _read_jsonl_file(f):
                    ts = entry.get("timestamp", "")
                    if ts >= cutoff_24h:
                        total_messages_24h += 1

        # Tasks
        tasks_dir = broker_dir / "tasks"
        if tasks_dir.exists():
            for f in sorted(tasks_dir.glob("*.json")):
                try:
                    task = json.loads(f.read_text("utf-8"))
                except (json.JSONDecodeError, OSError):
                    continue
                total_tasks += 1
                status = task.get("status", "")
                if status == "pending":
                    pending_tasks += 1
                elif status == "completed":
                    completed_tasks += 1
                elif status == "failed":
                    failed_tasks += 1

    total_finished = completed_tasks + failed_tasks
    failure_rate = (
        round(failed_tasks / total_finished * 100, 1)
        if total_finished
        else 0.0
    )

    handler._send_json({
        "timestamp": now.isoformat(),
        "active_agents": total_active_agents,
        "active_sessions": active_sessions_list,
        "channels": total_channels,
        "messages": {
            "last_24h": total_messages_24h,
        },
        "tasks": {
            "pending": pending_tasks,
            "completed": completed_tasks,
            "failed": failed_tasks,
            "total": total_tasks,
            "failure_rate": failure_rate,
        },
    })


def _handle_services(handler, params: dict) -> None:
    """GET /api/services — marketplace service listings across all brokers."""
    services: list[dict] = []
    category_filter = params.get("category", [None])[0]
    status_filter = params.get("status", [None])[0]

    for broker in _get_all_broker_dirs():
        services_dir = (
            Path(broker["broker_dir"]) / "marketplace" / "services"
        )
        if not services_dir.exists():
            continue
        proj = broker.get("project", "unknown")

        for f in sorted(services_dir.glob("*.json")):
            try:
                data = json.loads(f.read_text("utf-8"))
            except (json.JSONDecodeError, OSError):
                continue

            if category_filter and data.get("category") != category_filter:
                continue
            if status_filter and data.get("status") != status_filter:
                continue

            data["project"] = proj
            services.append(data)

    services.sort(key=lambda s: s.get("created", ""), reverse=True)
    handler._send_json(services)


def _handle_knowledge(handler, params: dict) -> None:
    """GET /api/knowledge — onboarding knowledge base articles across all brokers."""
    articles: list[dict] = []
    tag_filter = params.get("tag", [None])[0]

    for broker in _get_all_broker_dirs():
        knowledge_dir = (
            Path(broker["broker_dir"]) / "onboarding" / "knowledge"
        )
        if not knowledge_dir.exists():
            continue
        proj = broker.get("project", "unknown")

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

            if tag_filter:
                tags = [t.lower() for t in data.get("tags", [])]
                if tag_filter.lower() not in tags:
                    continue

            # Truncate long content for listing
            content = data.get("content", "")
            if isinstance(content, str) and len(content) > 300:
                data["content_preview"] = content[:300] + "..."
                del data["content"]

            data["project"] = proj
            articles.append(data)

    articles.sort(key=lambda a: a.get("updated", ""), reverse=True)
    handler._send_json(articles)


def _handle_bookings(handler, params: dict) -> None:
    """GET /api/bookings — active marketplace bookings across all brokers."""
    bookings: list[dict] = []
    status_filter = params.get("status", [None])[0]

    for broker in _get_all_broker_dirs():
        bookings_dir = (
            Path(broker["broker_dir"]) / "marketplace" / "bookings"
        )
        if not bookings_dir.exists():
            continue
        proj = broker.get("project", "unknown")

        for f in sorted(bookings_dir.glob("*.json")):
            try:
                data = json.loads(f.read_text("utf-8"))
            except (json.JSONDecodeError, OSError):
                continue

            if status_filter and data.get("status") != status_filter:
                continue

            data["project"] = proj
            bookings.append(data)

    bookings.sort(key=lambda b: b.get("created", ""), reverse=True)
    handler._send_json(bookings)


# ── Route table ───────────────────────────────────────────────────────────

_ENTERPRISE_ROUTES: dict[str, callable] = {
    "/api/relationships": _handle_relationships,
    "/api/utilization": _handle_utilization,
    "/api/leaderboard": _handle_leaderboard,
    "/api/system-health": _handle_system_health,
    "/api/services": _handle_services,
    "/api/knowledge": _handle_knowledge,
    "/api/bookings": _handle_bookings,
}


# ── Public API ────────────────────────────────────────────────────────────


def register_enterprise_routes(handler_instance, path: str, params: dict) -> bool:
    """Attempt to handle an enterprise route.

    Call this from DashboardHandler.do_GET() before the 404 fallback.

    Args:
        handler_instance: The DashboardHandler (``self`` inside do_GET).
        path: The URL path, already stripped of trailing slashes.
        params: Query parameters dict from ``parse_qs()``.

    Returns:
        True if the route was handled, False otherwise.
    """
    route_fn = _ENTERPRISE_ROUTES.get(path)
    if route_fn is None:
        return False
    route_fn(handler_instance, params)
    return True


def get_enterprise_tab_counts(broker_dirs: list[dict] | None = None) -> dict:
    """Return item counts for each enterprise module.

    Useful for rendering badge counts next to dashboard tabs.

    Args:
        broker_dirs: List of broker dicts (as returned by
            ``_get_all_broker_dirs()``).  If ``None``, the function
            discovers brokers automatically.

    Returns:
        Dict like ``{"relationships": 5, "services": 3, ...}``.
    """
    if broker_dirs is None:
        broker_dirs = _get_all_broker_dirs()

    counts = {
        "relationships": 0,
        "utilization": 0,
        "leaderboard": 0,
        "services": 0,
        "knowledge": 0,
        "bookings": 0,
        "system_health": 0,
    }

    for broker in broker_dirs:
        broker_dir = Path(broker["broker_dir"])

        # CRM relationships
        interactions_dir = broker_dir / "crm" / "interactions"
        if interactions_dir.exists():
            counts["relationships"] += sum(
                1
                for f in interactions_dir.glob("*.json")
                if not f.name.startswith("_")
            )

        # ERP utilization (agent profiles)
        agents_dir = broker_dir / "erp" / "agents"
        if agents_dir.exists():
            counts["utilization"] += sum(
                1 for _ in agents_dir.glob("*.json")
            )

        # Analytics — count active sessions as proxy for leaderboard
        sessions_dir = broker_dir / "sessions"
        if sessions_dir.exists():
            counts["leaderboard"] += sum(
                1 for _ in sessions_dir.glob("*.json")
            )

        # Marketplace services
        services_dir = broker_dir / "marketplace" / "services"
        if services_dir.exists():
            counts["services"] += sum(
                1 for _ in services_dir.glob("*.json")
            )

        # Onboarding knowledge articles
        knowledge_dir = broker_dir / "onboarding" / "knowledge"
        if knowledge_dir.exists():
            counts["knowledge"] += sum(
                1
                for f in knowledge_dir.glob("*.json")
                if not f.name.startswith("_")
            )

        # Marketplace bookings
        bookings_dir = broker_dir / "marketplace" / "bookings"
        if bookings_dir.exists():
            counts["bookings"] += sum(
                1 for _ in bookings_dir.glob("*.json")
            )

    return counts
