import { describe, it, expect } from "vitest";
import { matchRoute } from "../core/fetch-router";

/* ------------------------------------------------------------------------------------------------
 * basePath = "/"
 * --------------------------------------------------------------------------------------------- */

describe("matchRoute — basePath is /", () => {
  it("matches /info", () => {
    const result = matchRoute("/info", "/");
    expect(result).toEqual({ method: "info" });
  });

  it("matches /agent/:id/run", () => {
    const result = matchRoute("/agent/myAgent/run", "/");
    expect(result).toEqual({ method: "agent/run", agentId: "myAgent" });
  });

  it("matches /agent/:id/connect", () => {
    const result = matchRoute("/agent/myAgent/connect", "/");
    expect(result).toEqual({ method: "agent/connect", agentId: "myAgent" });
  });

  it("matches /agent/:id/stop/:threadId", () => {
    const result = matchRoute("/agent/myAgent/stop/t1", "/");
    expect(result).toEqual({
      method: "agent/stop",
      agentId: "myAgent",
      threadId: "t1",
    });
  });

  it("matches /transcribe", () => {
    const result = matchRoute("/transcribe", "/");
    expect(result).toEqual({ method: "transcribe" });
  });

  it("returns null for unknown path", () => {
    const result = matchRoute("/unknown", "/");
    expect(result).toBeNull();
  });
});

/* ------------------------------------------------------------------------------------------------
 * Trailing slash normalization
 * --------------------------------------------------------------------------------------------- */

describe("matchRoute — trailing slash normalization", () => {
  it("basePath with trailing slash matches", () => {
    const result = matchRoute("/api/copilotkit/info", "/api/copilotkit/");
    expect(result).toEqual({ method: "info" });
  });

  it("basePath without trailing slash matches", () => {
    const result = matchRoute("/api/copilotkit/info", "/api/copilotkit");
    expect(result).toEqual({ method: "info" });
  });
});

/* ------------------------------------------------------------------------------------------------
 * URL decoding
 * --------------------------------------------------------------------------------------------- */

describe("matchRoute — URL decoding", () => {
  it("decodes URL-encoded agentId", () => {
    const result = matchRoute("/api/agent/my%20agent/run", "/api");
    expect(result).toEqual({ method: "agent/run", agentId: "my agent" });
  });

  it("decodes URL-encoded threadId", () => {
    const result = matchRoute("/api/agent/myAgent/stop/thread%2F123", "/api");
    expect(result).toEqual({
      method: "agent/stop",
      agentId: "myAgent",
      threadId: "thread/123",
    });
  });

  it("handles agentId with special characters", () => {
    const result = matchRoute("/api/agent/agent%40domain.com/run", "/api");
    expect(result).toEqual({
      method: "agent/run",
      agentId: "agent@domain.com",
    });
  });

  it("handles already-decoded agentId", () => {
    const result = matchRoute("/api/agent/simple-agent/run", "/api");
    expect(result).toEqual({ method: "agent/run", agentId: "simple-agent" });
  });
});

/* ------------------------------------------------------------------------------------------------
 * Suffix matching (no basePath)
 * --------------------------------------------------------------------------------------------- */

describe("matchRoute — suffix matching (no basePath)", () => {
  it("matches info as last segment of any path", () => {
    const result = matchRoute("/some/deeply/nested/prefix/info");
    expect(result).toEqual({ method: "info" });
  });

  it("matches transcribe as last segment", () => {
    const result = matchRoute("/prefix/transcribe");
    expect(result).toEqual({ method: "transcribe" });
  });

  it("matches agent/:id/run as trailing pattern", () => {
    const result = matchRoute("/prefix/agent/myAgent/run");
    expect(result).toEqual({ method: "agent/run", agentId: "myAgent" });
  });

  it("matches agent/:id/connect as trailing pattern", () => {
    const result = matchRoute("/prefix/agent/myAgent/connect");
    expect(result).toEqual({ method: "agent/connect", agentId: "myAgent" });
  });

  it("matches agent/:id/stop/:threadId as trailing pattern", () => {
    const result = matchRoute("/prefix/agent/myAgent/stop/t1");
    expect(result).toEqual({
      method: "agent/stop",
      agentId: "myAgent",
      threadId: "t1",
    });
  });

  it("returns null for no matching suffix", () => {
    const result = matchRoute("/some/random/path/here");
    expect(result).toBeNull();
  });

  it("returns null for empty path", () => {
    const result = matchRoute("/");
    expect(result).toBeNull();
  });
});

/* ------------------------------------------------------------------------------------------------
 * basePath boundary matching
 * --------------------------------------------------------------------------------------------- */

describe("matchRoute — basePath boundary", () => {
  it("does not match if basePath is a prefix but not at segment boundary", () => {
    // /apicopilotkit/info should NOT match basePath /api
    const result = matchRoute("/apicopilotkit/info", "/api");
    expect(result).toBeNull();
  });

  it("matches when basePath is exact prefix at segment boundary", () => {
    const result = matchRoute("/api/info", "/api");
    expect(result).toEqual({ method: "info" });
  });

  it("basePath matches exactly with no trailing path", () => {
    // /api with basePath /api → remainder is "/" → no match (no known route for /)
    const result = matchRoute("/api", "/api");
    expect(result).toBeNull();
  });

  it("returns null when path does not start with basePath", () => {
    const result = matchRoute("/other/info", "/api");
    expect(result).toBeNull();
  });
});

/* ------------------------------------------------------------------------------------------------
 * Edge cases: segments and special patterns
 * --------------------------------------------------------------------------------------------- */

describe("matchRoute — segment edge cases", () => {
  it("handles double slashes by filtering empty segments", () => {
    // /api//info → segments: ["api", "info"] after filter(Boolean)
    const result = matchRoute("/api//info", "/api");
    expect(result).toEqual({ method: "info" });
  });

  it("handles path with only basePath + slash", () => {
    const result = matchRoute("/api/", "/api");
    expect(result).toBeNull();
  });

  it("matches with deep basePath", () => {
    const result = matchRoute("/v1/api/copilotkit/info", "/v1/api/copilotkit");
    expect(result).toEqual({ method: "info" });
  });

  it("matches agent route with deep basePath", () => {
    const result = matchRoute(
      "/v1/api/copilotkit/agent/default/run",
      "/v1/api/copilotkit",
    );
    expect(result).toEqual({ method: "agent/run", agentId: "default" });
  });

  it("agentId with hyphens, underscores, and dots", () => {
    const result = matchRoute("/api/agent/my-agent_v2.0/run", "/api");
    expect(result).toEqual({
      method: "agent/run",
      agentId: "my-agent_v2.0",
    });
  });

  it("single segment path without basePath matches info", () => {
    const result = matchRoute("/info");
    expect(result).toEqual({ method: "info" });
  });

  it("case-sensitive matching (INFO does not match info)", () => {
    const result = matchRoute("/api/INFO", "/api");
    expect(result).toBeNull();
  });

  it("case-sensitive matching (Agent vs agent)", () => {
    const result = matchRoute("/api/Agent/default/run", "/api");
    expect(result).toBeNull();
  });
});
