import express from "express";
import request from "supertest";
import { describe, expect, it, vi } from "vitest";
import type { AbstractAgent } from "@ag-ui/client";
import { Observable, of } from "rxjs";

import { createCopilotEndpointExpress } from "../express";
import { CopilotRuntime } from "../core/runtime";

vi.mock("../handlers/handle-run", () => ({
  handleRunAgent: vi
    .fn()
    .mockResolvedValue(new Response(null, { status: 200 })),
}));

vi.mock("../handlers/handle-connect", () => ({
  handleConnectAgent: vi
    .fn()
    .mockResolvedValue(new Response(null, { status: 200 })),
}));

vi.mock("../handlers/handle-stop", () => ({
  handleStopAgent: vi
    .fn()
    .mockResolvedValue(new Response(null, { status: 200 })),
}));

describe("CopilotEndpointExpress routing", () => {
  const createMockRuntime = () => {
    const createMockAgent = () => {
      const agent: unknown = {
        execute: async () => ({ events: [] }),
      };
      (agent as { clone: () => unknown }).clone = () => createMockAgent();
      return agent as AbstractAgent;
    };

    const runner = {
      run: () =>
        new Observable((observer) => {
          observer.next({});
          observer.complete();
          return () => undefined;
        }),
      connect: () => of({}),
      stop: async () => true,
    };

    return new CopilotRuntime({
      agents: {
        default: createMockAgent(),
        myAgent: createMockAgent(),
        agent123: createMockAgent(),
        "my-agent": createMockAgent(),
        my_agent: createMockAgent(),
        testAgent: createMockAgent(),
        test: createMockAgent(),
        "test%20agent": createMockAgent(),
        "test agent": createMockAgent(),
      },
      runner,
    });
  };

  const createApp = () => {
    const runtime = createMockRuntime();
    const app = express();
    app.use(createCopilotEndpointExpress({ runtime, basePath: "/" }));
    return { app, runtime };
  };

  describe("RunAgent route pattern", () => {
    it("matches simple agent name", async () => {
      const { app } = createApp();
      const response = await request(app)
        .post("/agent/myAgent/run")
        .set("Content-Type", "application/json")
        .send({
          messages: [],
          state: {},
          threadId: "thread-1",
        });

      expect(response.status).not.toBe(404);
    });

    it("matches hyphenated agent name", async () => {
      const { app } = createApp();
      const response = await request(app)
        .post("/agent/my-agent/run")
        .set("Content-Type", "application/json")
        .send({
          messages: [],
          state: {},
          threadId: "thread-1",
        });

      expect(response.status).not.toBe(404);
    });

    it("matches underscored agent name", async () => {
      const { app } = createApp();
      const response = await request(app)
        .post("/agent/my_agent/run")
        .set("Content-Type", "application/json")
        .send({
          messages: [],
          state: {},
          threadId: "thread-1",
        });

      expect(response.status).not.toBe(404);
    });

    it("returns 404 for empty agent name", async () => {
      const { app } = createApp();
      const response = await request(app)
        .post("/agent//run")
        .set("Content-Type", "application/json")
        .send({
          messages: [],
          state: {},
          threadId: "thread-1",
        });

      expect(response.status).toBe(404);
    });
  });

  describe("Runtime info route", () => {
    it("returns 200 for /info", async () => {
      const { app } = createApp();
      const response = await request(app).get("/info");

      expect(response.status).toBe(200);
      expect(response.body).toHaveProperty("version");
    });

    it("returns 404 for non-info path", async () => {
      const { app } = createApp();
      const response = await request(app).get("/agents");

      expect(response.status).toBe(404);
    });
  });

  describe("Transcribe route", () => {
    it("matches /transcribe", async () => {
      const { app } = createApp();
      const response = await request(app)
        .post("/transcribe")
        .set("Content-Type", "application/json")
        .send({});

      expect(response.status).not.toBe(404);
    });
  });

  describe("Unmatched routes", () => {
    it("returns 404 for root path", async () => {
      const { app } = createApp();
      const response = await request(app).get("/");

      expect(response.status).toBe(404);
    });

    it("returns 404 for unknown paths", async () => {
      const { app } = createApp();
      const response = await request(app).get("/unknown/path");

      expect(response.status).toBe(404);
    });
  });
});
