import { describe, expect, it } from "vitest";
import {
  buildThreadAwareOutboundSessionRoute,
  recoverCurrentThreadSessionId,
  type ChannelOutboundSessionRoute,
} from "./core.js";

function baseRoute(
  overrides: Partial<ChannelOutboundSessionRoute> = {},
): ChannelOutboundSessionRoute {
  return {
    sessionKey: "agent:main:workspace:channel:c123",
    baseSessionKey: "agent:main:workspace:channel:c123",
    peer: { kind: "channel", id: "c123" },
    chatType: "channel",
    from: "workspace:channel:c123",
    to: "channel:c123",
    ...overrides,
  };
}

describe("buildThreadAwareOutboundSessionRoute", () => {
  it("uses replyToId before threadId and recovered current-session thread by default", () => {
    const route = buildThreadAwareOutboundSessionRoute({
      route: baseRoute(),
      replyToId: "reply-1",
      threadId: "thread-1",
      currentSessionKey: "agent:main:workspace:channel:c123:thread:current-1",
    });

    expect(route).toEqual(
      baseRoute({
        sessionKey: "agent:main:workspace:channel:c123:thread:reply-1",
        threadId: "reply-1",
      }),
    );
  });

  it("supports provider-specific threadId-first precedence", () => {
    const route = buildThreadAwareOutboundSessionRoute({
      route: baseRoute(),
      replyToId: "reply-1",
      threadId: "thread-1",
      precedence: ["threadId", "replyToId", "currentSession"],
    });

    expect(route).toEqual(
      baseRoute({
        sessionKey: "agent:main:workspace:channel:c123:thread:thread-1",
        threadId: "thread-1",
      }),
    );
  });

  it("keeps numeric delivery thread ids on the route while stringifying the session suffix", () => {
    const route = buildThreadAwareOutboundSessionRoute({
      route: baseRoute(),
      threadId: 99,
    });

    expect(route).toEqual(
      baseRoute({
        sessionKey: "agent:main:workspace:channel:c123:thread:99",
        threadId: 99,
      }),
    );
  });

  it("recovers a current-session thread only when the base session matches", () => {
    expect(
      recoverCurrentThreadSessionId({
        route: baseRoute(),
        currentSessionKey: "agent:main:workspace:channel:c123:thread:current-1",
      }),
    ).toBe("current-1");
    expect(
      recoverCurrentThreadSessionId({
        route: baseRoute(),
        currentSessionKey: "agent:main:workspace:channel:other:thread:current-1",
      }),
    ).toBeUndefined();
  });

  it("lets providers veto current-session recovery", () => {
    const route = buildThreadAwareOutboundSessionRoute({
      route: baseRoute(),
      currentSessionKey: "agent:main:workspace:channel:c123:thread:current-1",
      canRecoverCurrentThread: () => false,
    });

    expect(route).toEqual(
      baseRoute({
        sessionKey: "agent:main:workspace:channel:c123",
      }),
    );
  });

  it("preserves provider-specific thread case when requested", () => {
    const route = buildThreadAwareOutboundSessionRoute({
      route: baseRoute(),
      threadId: "$EventID:Example.Org",
      normalizeThreadId: (threadId) => threadId,
    });

    expect(route).toEqual(
      baseRoute({
        sessionKey: "agent:main:workspace:channel:c123:thread:$EventID:Example.Org",
        threadId: "$EventID:Example.Org",
      }),
    );
  });

  it("can carry a delivery thread without adding a session suffix", () => {
    const route = buildThreadAwareOutboundSessionRoute({
      route: baseRoute(),
      threadId: "thread-1",
      useSuffix: false,
    });

    expect(route).toEqual(
      baseRoute({
        sessionKey: "agent:main:workspace:channel:c123",
        threadId: "thread-1",
      }),
    );
  });
});
