"""REST API for Dobby meta-agent orchestrator.

This package exposes :func:`create_app` (the aiohttp Application factory),
:class:`EventBus`, and backward-compatible re-exports of ``slack_interactions``
and ``slack_command`` so that existing ``from dobby.core.api import …`` statements
continue to work after the split into sub-modules.
"""

from __future__ import annotations

import asyncio
import contextlib
import logging
import os
from typing import Any

from aiohttp import web

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Event bus for SSE
# ---------------------------------------------------------------------------

class EventBus:
    """Simple pub/sub for broadcasting events to SSE clients."""

    def __init__(self) -> None:
        self._subscribers: list[asyncio.Queue] = []

    def subscribe(self) -> asyncio.Queue:
        q: asyncio.Queue = asyncio.Queue()
        self._subscribers.append(q)
        return q

    def unsubscribe(self, q: asyncio.Queue) -> None:
        with contextlib.suppress(ValueError):
            self._subscribers.remove(q)

    async def publish(self, event_type: str, data: dict[str, Any]) -> None:
        payload = {"event": event_type, "data": data}
        for q in list(self._subscribers):
            with contextlib.suppress(asyncio.QueueFull):
                q.put_nowait(payload)


# ---------------------------------------------------------------------------
# Auth middleware
# ---------------------------------------------------------------------------

@web.middleware
async def auth_middleware(request: web.Request, handler):
    api_key = request.app.get("api_key")
    if api_key is None:
        return await handler(request)
    # Skip auth for health check, dashboard, assets, SSE events, and Slack interactions
    exempt = {
        "/health", "/dashboard", "/events", "/utilization", "/usage",
        "/slack/interactions", "/slack/command",
    }
    if (
        request.path in exempt
        or request.path.startswith("/assets/")
        or request.path.startswith("/static/")
    ):
        return await handler(request)
    auth_header = request.headers.get("Authorization", "")
    if auth_header == f"Bearer {api_key}":
        return await handler(request)
    raise web.HTTPUnauthorized(reason="Invalid or missing API key")


# ---------------------------------------------------------------------------
# Backward-compatible re-exports
# ---------------------------------------------------------------------------

from dobby.core.api.slack_handlers import slack_command, slack_interactions  # noqa: E402, F401

# ---------------------------------------------------------------------------
# App factory
# ---------------------------------------------------------------------------

def create_app(
    store,
    dispatcher=None,
    results_dir: str = "results",
    api_key: str | None = None,
    event_bus: EventBus | None = None,
    slack_bot=None,
    hub_url: str = "",
    github_url: str = "",
) -> web.Application:
    """Create and return the aiohttp Application."""
    # Import all handler sub-modules
    from dobby.core.api.memories import delete_memory, get_memory, list_memories, save_memory
    from dobby.core.api.planner import (
        handle_planner_generate,
        handle_planner_settings,
        handle_planner_today,
    )
    from dobby.core.api.schedule import get_schedule
    from dobby.core.api.spokes import (
        list_spokes,
        register_spoke,
        spoke_append_log,
        spoke_claim_task,
        spoke_heartbeat,
        spoke_submit_result,
    )
    from dobby.core.api.sse import sse_events
    from dobby.core.api.stats import stats, stats_cost
    from dobby.core.api.tasks import (
        create_batch,
        create_task,
        export_results,
        get_batch,
        get_project,
        get_result,
        get_task,
        get_task_artifacts,
        get_task_detail_status,
        get_task_events,
        get_task_logs,
        get_task_session,
        health,
        list_batches,
        list_projects,
        list_results,
        list_tasks,
        patch_task,
        register_project,
        remote_status,
        retry_task,
    )
    from dobby.core.api.todos import create_todo, delete_todo, get_todo, list_todos, patch_todo
    from dobby.core.api.trajectory import (
        export_traces,
        get_trace,
        get_trace_execution,
        get_trace_raw,
        list_traces,
        trace_stats,
    )
    from dobby.core.api.usage import usage_overview, utilization, utilization_history

    middlewares = []
    if api_key is not None:
        middlewares.append(auth_middleware)

    app = web.Application(middlewares=middlewares)
    app["store"] = store
    app["results_dir"] = results_dir
    if api_key is not None:
        app["api_key"] = api_key
    if event_bus is not None:
        app["event_bus"] = event_bus
    if dispatcher is not None:
        app["dispatcher"] = dispatcher
    if slack_bot is not None:
        app["slack_bot"] = slack_bot
    app["hub_url"] = hub_url
    app["github_url"] = github_url

    # Create Slack notifier for spoke result notifications
    slack_bot_token = os.environ.get("SLACK_BOT_TOKEN")
    slack_channel = os.environ.get("SLACK_CHANNEL", "")
    slack_report_channel = os.environ.get("SLACK_REPORT_CHANNEL", slack_channel)
    if slack_bot_token and slack_channel:
        try:
            from slack_sdk.web.async_client import AsyncWebClient
            slack_client = AsyncWebClient(token=slack_bot_token)
            from dobby.slack.notifier import SlackNotifier
            app["slack_notifier"] = SlackNotifier(
                slack_client, slack_channel,
                hub_url=hub_url, report_channel_id=slack_report_channel,
            )
            logger.info("Slack notifier: comm=%s report=%s", slack_channel, slack_report_channel)
        except Exception as exc:
            logger.warning("Failed to create Slack notifier: %s", exc)
    elif slack_bot_token:
        # No channel specified — try to create notifier anyway, it will post to threads only
        try:
            from slack_sdk.web.async_client import AsyncWebClient
            slack_client = AsyncWebClient(token=slack_bot_token)
            from dobby.slack.notifier import SlackNotifier
            app["slack_notifier"] = SlackNotifier(slack_client, "", hub_url=hub_url)
            logger.info("Slack notifier initialized (no default channel, thread-only)")
        except Exception as exc:
            logger.warning("Failed to create Slack notifier: %s", exc)

    app.router.add_get("/health", health)
    app.router.add_get("/stats", stats)
    app.router.add_get("/stats/cost", stats_cost)
    app.router.add_get("/remote-status", remote_status)

    app.router.add_post("/tasks", create_task)
    app.router.add_get("/tasks", list_tasks)
    app.router.add_get("/tasks/{id}", get_task)
    app.router.add_patch("/tasks/{id}", patch_task)
    app.router.add_post("/tasks/{id}/retry", retry_task)
    app.router.add_get("/tasks/{id}/events", get_task_events)
    app.router.add_get("/tasks/{id}/artifacts", get_task_artifacts)
    app.router.add_get("/tasks/{id}/detail-status", get_task_detail_status)
    app.router.add_get("/tasks/{id}/logs", get_task_logs)
    app.router.add_get("/tasks/{id}/session", get_task_session)

    app.router.add_post("/projects", register_project)
    app.router.add_get("/projects", list_projects)
    app.router.add_get("/projects/{name}", get_project)

    app.router.add_get("/results", list_results)
    app.router.add_get("/results/{task_id}", get_result)

    app.router.add_post("/batches", create_batch)
    app.router.add_get("/batches", list_batches)
    app.router.add_get("/batches/{batch_id}", get_batch)

    app.router.add_get("/export", export_results)

    # Memories
    app.router.add_get("/memories", list_memories)
    app.router.add_get("/memories/{key}", get_memory)
    app.router.add_post("/memories", save_memory)
    app.router.add_delete("/memories/{key}", delete_memory)

    # Todos
    app.router.add_post("/todos", create_todo)
    app.router.add_get("/todos", list_todos)
    app.router.add_get("/todos/{id}", get_todo)
    app.router.add_patch("/todos/{id}", patch_todo)
    app.router.add_delete("/todos/{id}", delete_todo)

    # Spoke endpoints
    app.router.add_post("/spokes/register", register_spoke)
    app.router.add_get("/spokes", list_spokes)
    app.router.add_post("/spokes/{id}/heartbeat", spoke_heartbeat)
    app.router.add_post("/spokes/{id}/claim", spoke_claim_task)
    app.router.add_post("/spokes/{id}/result", spoke_submit_result)
    app.router.add_post("/tasks/{task_id}/log-append", spoke_append_log)

    app.router.add_get("/utilization", utilization)
    app.router.add_get("/utilization/history", utilization_history)
    app.router.add_get("/usage", usage_overview)

    app.router.add_get("/events", sse_events)

    # Schedule overview
    app.router.add_get("/schedule", get_schedule)

    # Planner
    app.router.add_get("/planner/today", handle_planner_today)
    app.router.add_post("/planner/generate", handle_planner_generate)
    app.router.add_get("/planner/settings", handle_planner_settings)

    # Traces (trajectory tracking)
    app.router.add_get("/traces", list_traces)
    app.router.add_get("/trace/{trace_id}", get_trace)
    app.router.add_get("/trace/{trace_id}/execution", get_trace_execution)
    app.router.add_get("/trace/{trace_id}/raw", get_trace_raw)
    app.router.add_get("/stats/traces", trace_stats)
    app.router.add_get("/export/traces", export_traces)

    return app
