import { describe, expect, test } from "bun:test"
import { makeACPFixture, makeSession, secondModel, type FixtureContext, type FixtureRequest } from "./service-fixture"

describe("acp service prompt routing and usage", () => {
  test("routes slash commands, skills, and compact through their session endpoints", async () => {
    await using fixture = makeACPFixture({
      fetch(request, context) {
        if (request.method === "POST" && request.path === "/api/session") {
          return Response.json({ data: makeSession("ses_routes") })
        }
        if (request.method === "GET" && request.path === "/api/session/ses_routes") {
          return Response.json({ data: makeSession("ses_routes") })
        }
        if (request.method === "POST" && request.path === "/api/session/ses_routes/command") {
          const id = requestID(request)
          completeTurn(context, "ses_routes", {
            id: `evt_${id}`,
            type: "session.input.promoted",
            data: { sessionID: "ses_routes", inputID: id },
          })
          return Response.json({ data: {} })
        }
        if (request.method === "POST" && request.path === "/api/session/ses_routes/skill") {
          const id = requestID(request)
          completeTurn(context, "ses_routes", {
            id: id.replace(/^msg_/, "evt_"),
            type: "session.skill.activated",
            data: { sessionID: "ses_routes", skill: "verify" },
          })
          return new Response(null, { status: 204 })
        }
        if (request.method === "POST" && request.path === "/api/session/ses_routes/compact") {
          const id = requestID(request)
          completeTurn(context, "ses_routes", {
            id: `evt_${id}`,
            type: "session.compaction.admitted",
            data: { sessionID: "ses_routes", inputID: id },
          })
          return Response.json({ data: {} })
        }
        return undefined
      },
    })
    const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })

    const commandResult = await fixture.service.prompt({
      sessionId: session.sessionId,
      prompt: [{ type: "text", text: "/review now" }],
    })
    const skillResult = await fixture.service.prompt({
      sessionId: session.sessionId,
      prompt: [{ type: "text", text: "/verify" }],
    })
    const compactResult = await fixture.service.prompt({
      sessionId: session.sessionId,
      prompt: [{ type: "text", text: "/compact" }],
    })

    expect([commandResult.stopReason, skillResult.stopReason, compactResult.stopReason]).toEqual([
      "end_turn",
      "end_turn",
      "end_turn",
    ])
    const command = fixture.requests.find((request) => request.path === "/api/session/ses_routes/command")
    const skill = fixture.requests.find((request) => request.path === "/api/session/ses_routes/skill")
    const compact = fixture.requests.find((request) => request.path === "/api/session/ses_routes/compact")
    expect(command?.body).toMatchObject({
      id: expect.any(String),
      command: "review",
      arguments: "now",
      files: [],
      delivery: "steer",
    })
    expect(skill?.body).toMatchObject({ id: expect.any(String), skill: "verify" })
    expect(compact?.body).toMatchObject({ id: expect.any(String) })
    expect(fixture.requests.some((request) => request.path === "/api/session/ses_routes/prompt")).toBe(false)
  })

  test("returns turn usage and publishes current context usage with cumulative session cost", async () => {
    const assistantTokens = {
      input: 100,
      output: 40,
      reasoning: 7,
      cache: { read: 11, write: 13 },
    }
    await using fixture = makeACPFixture({
      fetch(request, context) {
        if (request.method === "POST" && request.path === "/api/session") {
          return Response.json({ data: makeSession("ses_usage") })
        }
        if (request.method === "POST" && request.path === "/api/session/ses_usage/model") {
          return new Response(null, { status: 204 })
        }
        if (request.method === "POST" && request.path === "/api/session/ses_usage/prompt") {
          const id = requestID(request)
          context.send({
            id: `evt_${id}`,
            type: "session.input.promoted",
            data: { sessionID: "ses_usage", inputID: id },
          })
          context.send({
            id: "evt_step",
            type: "session.step.ended",
            data: {
              sessionID: "ses_usage",
              assistantMessageID: "msg_assistant",
              finish: "stop",
              cost: 0.5,
              tokens: assistantTokens,
            },
          })
          context.send({
            id: "evt_done",
            type: "session.execution.succeeded",
            data: { sessionID: "ses_usage" },
          })
          return Response.json({ data: {} })
        }
        if (request.method === "GET" && request.path === "/api/session/ses_usage/message/msg_assistant") {
          return Response.json({
            data: {
              id: "msg_assistant",
              type: "assistant",
              agent: "build",
              model: { providerID: "test", id: secondModel.id },
              content: [{ type: "text", text: "done" }],
              finish: "stop",
              tokens: assistantTokens,
              time: { created: 1, completed: 2 },
            },
          })
        }
        if (request.method === "GET" && request.path === "/api/session/ses_usage") {
          return Response.json({
            data: makeSession("ses_usage", {
              model: { providerID: "test", id: secondModel.id },
              cost: 3.5,
              tokens: { input: 120, output: 50, reasoning: 8, cache: { read: 30, write: 4 } },
            }),
          })
        }
        return undefined
      },
    })
    const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })
    await fixture.service.setSessionConfigOption({
      sessionId: session.sessionId,
      configId: "model",
      value: "test/second-model",
    })

    const response = await fixture.service.prompt({
      sessionId: session.sessionId,
      prompt: [{ type: "text", text: "hello" }],
    })

    expect(response).toEqual({
      stopReason: "end_turn",
      usage: {
        inputTokens: 100,
        outputTokens: 40,
        thoughtTokens: 7,
        cachedReadTokens: 11,
        cachedWriteTokens: 13,
        totalTokens: 171,
      },
      _meta: {},
    })
    expect(fixture.updates.filter((item) => item.update.sessionUpdate === "usage_update")).toEqual([
      {
        sessionId: "ses_usage",
        update: {
          sessionUpdate: "usage_update",
          used: 171,
          size: 200_000,
          cost: { amount: 3.5, currency: "USD" },
        },
      },
    ])
  })

  test("does not fail a completed prompt when the usage refresh fails", async () => {
    await using fixture = makeACPFixture({
      fetch(request, context) {
        if (request.method === "POST" && request.path === "/api/session") {
          return Response.json({ data: makeSession("ses_usage_failure") })
        }
        if (request.method === "POST" && request.path === "/api/session/ses_usage_failure/prompt") {
          const id = requestID(request)
          context.send({
            id: `evt_${id}`,
            type: "session.input.promoted",
            data: { sessionID: "ses_usage_failure", inputID: id },
          })
          context.send({
            id: "evt_step_failure",
            type: "session.step.ended",
            data: {
              sessionID: "ses_usage_failure",
              assistantMessageID: "msg_usage_failure",
              finish: "stop",
              cost: 0,
              tokens: { input: 3, output: 2, reasoning: 0, cache: { read: 0, write: 0 } },
            },
          })
          context.send({
            id: "evt_done_failure",
            type: "session.execution.succeeded",
            data: { sessionID: "ses_usage_failure" },
          })
          return Response.json({ data: {} })
        }
        if (request.method === "GET" && request.path === "/api/session/ses_usage_failure/message/msg_usage_failure") {
          return Response.json({
            data: {
              id: "msg_usage_failure",
              type: "assistant",
              agent: "build",
              model: { providerID: "test", id: "test-model" },
              content: [],
              finish: "stop",
              tokens: { input: 3, output: 2, reasoning: 0, cache: { read: 0, write: 0 } },
              time: { created: 1, completed: 2 },
            },
          })
        }
        if (request.method === "GET" && request.path === "/api/session/ses_usage_failure") {
          return new Response(null, { status: 500 })
        }
        return undefined
      },
    })
    const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })

    const response = await fixture.service.prompt({
      sessionId: session.sessionId,
      prompt: [{ type: "text", text: "hello" }],
    })

    expect(response.stopReason).toBe("end_turn")
    expect(fixture.updates.some((item) => item.update.sessionUpdate === "usage_update")).toBe(false)
  })
})

function requestID(request: FixtureRequest) {
  if (!request.body || typeof request.body !== "object") throw new Error(`missing body for ${request.path}`)
  const id = Reflect.get(request.body, "id")
  if (typeof id !== "string") throw new Error(`missing prompt id for ${request.path}`)
  return id
}

function completeTurn(context: FixtureContext, sessionID: string, start: unknown) {
  context.send(start)
  context.send({
    id: `evt_done_${sessionID}`,
    type: "session.execution.succeeded",
    data: { sessionID },
  })
}
