defmodule Symbiont.API do
  @moduledoc """
  HTTP API for Symbiont — drop-in replacement for the Python FastAPI server.

  Endpoints:
    POST /task          — Submit and execute a task immediately
    POST /queue         — Add a task to the persistent queue
    GET  /status        — Health check + system overview
    GET  /ledger        — Recent inference calls
    GET  /ledger/stats  — Aggregate cost & usage
    GET  /health        — Simple liveness probe

  Engram endpoints:
    GET  /engram/sitrep           — Situation report (world state + active sessions)
    GET  /engram/world            — Raw world state markdown
    PUT  /engram/world            — Update world state
    POST /engram/sessions         — Register a new session
    GET  /engram/sessions         — List active sessions
    GET  /engram/sessions/recent  — Recently completed sessions
    GET  /engram/sessions/:id     — Get a single session
    POST /engram/sessions/:id/heartbeat — Update heartbeat
    POST /engram/sessions/:id/complete  — Mark session completed
    POST /engram/sessions/:id/log       — Add a log entry
    GET  /engram/sessions/:id/logs      — Get session logs
    POST /engram/locks            — Lock a resource
    DELETE /engram/locks          — Release a resource lock
    GET  /engram/locks/:resource  — Check locks on a resource
    GET  /engram/stats            — Engram stats
  """
  use Plug.Router

  plug(Plug.Logger)
  plug(:match)
  plug(Plug.Parsers, parsers: [:json], json_decoder: Jason)
  plug(:dispatch)

  # ── Task Endpoints ──────────────────────────────────────────────────────

  post "/task" do
    task = conn.body_params["task"]
    force_tier = conn.body_params["force_tier"]

    if is_nil(task) or task == "" do
      send_json(conn, 400, %{error: "missing 'task' field"})
    else
      opts = if force_tier, do: [force_tier: force_tier], else: []

      case Symbiont.Router.route_and_execute(task, opts) do
        {:ok, result} ->
          response = %{
            id: "task-#{System.system_time(:second)}",
            task: task,
            model: result.model,
            result: result.result,
            elapsed_seconds: result.elapsed_seconds,
            input_tokens: result.input_tokens,
            output_tokens: result.output_tokens,
            estimated_cost_usd: result.estimated_cost_usd,
            timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
          }

          send_json(conn, 200, response)

        {:error, reason} ->
          send_json(conn, 500, %{error: inspect(reason)})
      end
    end
  end

  # ── Queue Endpoints ─────────────────────────────────────────────────────

  post "/queue" do
    task = conn.body_params["task"]
    priority = conn.body_params["priority"] || "normal"

    if is_nil(task) or task == "" do
      send_json(conn, 400, %{error: "missing 'task' field"})
    else
      {:ok, task_id} = Symbiont.Queue.enqueue(task, priority)
      queue_size = Symbiont.Queue.size()

      send_json(conn, 200, %{
        id: task_id,
        status: "queued",
        position: queue_size
      })
    end
  end

  # ── Status Endpoints ────────────────────────────────────────────────────

  get "/status" do
    ledger_stats = Symbiont.Ledger.stats()
    queue_size = Symbiont.Queue.size()
    last_heartbeat = Symbiont.Heartbeat.last_snapshot()

    response = %{
      status: "healthy",
      runtime: "elixir/otp",
      queue_size: queue_size,
      last_heartbeat: last_heartbeat && last_heartbeat["timestamp"],
      total_calls: ledger_stats["total_calls"],
      total_cost_estimated_usd: ledger_stats["total_cost_estimated_usd"],
      by_model: ledger_stats["by_model"]
    }

    send_json(conn, 200, response)
  end

  get "/ledger" do
    entries = Symbiont.Ledger.recent(50)
    send_json(conn, 200, %{entries: entries, count: length(entries)})
  end

  get "/ledger/stats" do
    stats = Symbiont.Ledger.stats()
    send_json(conn, 200, stats)
  end

  get "/health" do
    send_json(conn, 200, %{status: "ok", runtime: "elixir/otp"})
  end

  # ── Engram Endpoints ────────────────────────────────────────────────────

  get "/engram/sitrep" do
    report = Symbiont.Engram.sitrep()
    send_json(conn, 200, %{sitrep: report})
  end

  get "/engram/world" do
    content = Symbiont.Engram.get_world_state()
    send_json(conn, 200, %{content: content})
  end

  put "/engram/world" do
    content = conn.body_params["content"]
    updated_by = conn.body_params["updated_by"]

    if is_nil(content) do
      send_json(conn, 400, %{error: "missing 'content' field"})
    else
      :ok = Symbiont.Engram.set_world_state(content, updated_by)
      send_json(conn, 200, %{status: "ok"})
    end
  end

  post "/engram/sessions" do
    session_type = conn.body_params["session_type"]
    summary = conn.body_params["summary"]
    metadata = conn.body_params["metadata"]

    if is_nil(session_type) or is_nil(summary) do
      send_json(conn, 400, %{error: "missing 'session_type' and/or 'summary'"})
    else
      {:ok, sid} = Symbiont.Engram.register(session_type, summary, metadata)
      send_json(conn, 200, %{session_id: sid, status: "active"})
    end
  end

  get "/engram/sessions" do
    sessions = Symbiont.Engram.get_active_sessions()
    send_json(conn, 200, %{sessions: sessions, count: length(sessions)})
  end

  get "/engram/sessions/recent" do
    hours = parse_int(conn.query_params["hours"], 24)
    sessions = Symbiont.Engram.get_recent_sessions(hours)
    send_json(conn, 200, %{sessions: sessions, count: length(sessions)})
  end

  # Note: this must come AFTER /engram/sessions/recent to avoid matching "recent" as :id
  get "/engram/sessions/:id" do
    case Symbiont.Engram.get_session(id) do
      {:ok, session} -> send_json(conn, 200, session)
      {:error, :not_found} -> send_json(conn, 404, %{error: "session not found"})
    end
  end

  post "/engram/sessions/:id/heartbeat" do
    summary = conn.body_params["summary"]
    :ok = Symbiont.Engram.heartbeat(id, summary)
    send_json(conn, 200, %{status: "ok"})
  end

  post "/engram/sessions/:id/complete" do
    completion_summary = conn.body_params["summary"]

    if is_nil(completion_summary) do
      send_json(conn, 400, %{error: "missing 'summary' field"})
    else
      :ok = Symbiont.Engram.complete(id, completion_summary)
      send_json(conn, 200, %{status: "completed"})
    end
  end

  post "/engram/sessions/:id/log" do
    entry = conn.body_params["entry"]

    if is_nil(entry) do
      send_json(conn, 400, %{error: "missing 'entry' field"})
    else
      :ok = Symbiont.Engram.log(id, entry)
      send_json(conn, 200, %{status: "ok"})
    end
  end

  get "/engram/sessions/:id/logs" do
    limit = parse_int(conn.query_params["limit"], 20)
    logs = Symbiont.Engram.get_session_logs(id, limit)
    send_json(conn, 200, %{logs: logs, count: length(logs)})
  end

  post "/engram/locks" do
    session_id = conn.body_params["session_id"]
    resource = conn.body_params["resource"]
    note = conn.body_params["note"]

    if is_nil(session_id) or is_nil(resource) do
      send_json(conn, 400, %{error: "missing 'session_id' and/or 'resource'"})
    else
      :ok = Symbiont.Engram.lock_resource(session_id, resource, note)
      send_json(conn, 200, %{status: "locked"})
    end
  end

  delete "/engram/locks" do
    session_id = conn.body_params["session_id"]
    resource = conn.body_params["resource"]

    if is_nil(session_id) or is_nil(resource) do
      send_json(conn, 400, %{error: "missing 'session_id' and/or 'resource'"})
    else
      :ok = Symbiont.Engram.release_resource(session_id, resource)
      send_json(conn, 200, %{status: "released"})
    end
  end

  get "/engram/locks/:resource" do
    locks = Symbiont.Engram.check_locks(URI.decode(resource))
    send_json(conn, 200, %{locks: locks, count: length(locks)})
  end

  get "/engram/stats" do
    stats = Symbiont.Engram.stats()
    send_json(conn, 200, stats)
  end

  # ── Fallback ────────────────────────────────────────────────────────────

  match _ do
    send_json(conn, 404, %{error: "not found"})
  end

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

  defp send_json(conn, status, body) do
    conn
    |> put_resp_content_type("application/json")
    |> send_resp(status, Jason.encode!(body))
  end

  defp parse_int(nil, default), do: default
  defp parse_int(str, default) do
    case Integer.parse(str) do
      {n, _} -> n
      :error -> default
    end
  end
end
