import { Test, TestingModule } from "@nestjs/testing";
import { MatrixService } from "./matrix.service";
import { MatrixRoomService } from "./matrix-room.service";
import { StitcherService } from "../../stitcher/stitcher.service";
import { CommandParserService } from "../parser/command-parser.service";
import { vi, describe, it, expect, beforeEach } from "vitest";
import type { ChatMessage } from "../interfaces";

// Mock matrix-bot-sdk
const mockMessageCallbacks: Array<(roomId: string, event: Record<string, unknown>) => void> = [];
const mockEventCallbacks: Array<(roomId: string, event: Record<string, unknown>) => void> = [];

const mockClient = {
  start: vi.fn().mockResolvedValue(undefined),
  stop: vi.fn(),
  on: vi
    .fn()
    .mockImplementation(
      (event: string, callback: (roomId: string, evt: Record<string, unknown>) => void) => {
        if (event === "room.message") {
          mockMessageCallbacks.push(callback);
        }
        if (event === "room.event") {
          mockEventCallbacks.push(callback);
        }
      }
    ),
  sendMessage: vi.fn().mockResolvedValue("$event-id-123"),
  sendEvent: vi.fn().mockResolvedValue("$event-id-456"),
};

vi.mock("matrix-bot-sdk", () => {
  return {
    MatrixClient: class MockMatrixClient {
      start = mockClient.start;
      stop = mockClient.stop;
      on = mockClient.on;
      sendMessage = mockClient.sendMessage;
      sendEvent = mockClient.sendEvent;
    },
    SimpleFsStorageProvider: class MockStorageProvider {
      constructor(_filename: string) {
        // No-op for testing
      }
    },
    AutojoinRoomsMixin: {
      setupOnClient: vi.fn(),
    },
  };
});

describe("MatrixService", () => {
  let service: MatrixService;
  let stitcherService: StitcherService;
  let commandParser: CommandParserService;
  let matrixRoomService: MatrixRoomService;

  const mockStitcherService = {
    dispatchJob: vi.fn().mockResolvedValue({
      jobId: "test-job-id",
      queueName: "main",
      status: "PENDING",
    }),
    trackJobEvent: vi.fn().mockResolvedValue(undefined),
  };

  const mockMatrixRoomService = {
    getWorkspaceForRoom: vi.fn().mockResolvedValue(null),
    getRoomForWorkspace: vi.fn().mockResolvedValue(null),
    provisionRoom: vi.fn().mockResolvedValue(null),
    linkWorkspaceToRoom: vi.fn().mockResolvedValue(undefined),
    unlinkWorkspace: vi.fn().mockResolvedValue(undefined),
  };

  beforeEach(async () => {
    // Set environment variables for testing
    process.env.MATRIX_HOMESERVER_URL = "https://matrix.example.com";
    process.env.MATRIX_ACCESS_TOKEN = "test-access-token";
    process.env.MATRIX_BOT_USER_ID = "@mosaic-bot:example.com";
    process.env.MATRIX_CONTROL_ROOM_ID = "!test-room:example.com";
    process.env.MATRIX_WORKSPACE_ID = "test-workspace-id";

    // Clear callbacks
    mockMessageCallbacks.length = 0;
    mockEventCallbacks.length = 0;

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MatrixService,
        CommandParserService,
        {
          provide: StitcherService,
          useValue: mockStitcherService,
        },
        {
          provide: MatrixRoomService,
          useValue: mockMatrixRoomService,
        },
      ],
    }).compile();

    service = module.get<MatrixService>(MatrixService);
    stitcherService = module.get<StitcherService>(StitcherService);
    commandParser = module.get<CommandParserService>(CommandParserService);
    matrixRoomService = module.get(MatrixRoomService) as MatrixRoomService;

    // Clear all mocks
    vi.clearAllMocks();
  });

  describe("Connection Management", () => {
    it("should connect to Matrix", async () => {
      await service.connect();

      expect(mockClient.start).toHaveBeenCalled();
    });

    it("should disconnect from Matrix", async () => {
      await service.connect();
      await service.disconnect();

      expect(mockClient.stop).toHaveBeenCalled();
    });

    it("should check connection status", async () => {
      expect(service.isConnected()).toBe(false);

      await service.connect();
      expect(service.isConnected()).toBe(true);

      await service.disconnect();
      expect(service.isConnected()).toBe(false);
    });
  });

  describe("Message Handling", () => {
    it("should send a message to a room", async () => {
      await service.connect();
      await service.sendMessage("!test-room:example.com", "Hello, Matrix!");

      expect(mockClient.sendMessage).toHaveBeenCalledWith("!test-room:example.com", {
        msgtype: "m.text",
        body: "Hello, Matrix!",
      });
    });

    it("should throw error if client is not connected", async () => {
      await expect(service.sendMessage("!room:example.com", "Test")).rejects.toThrow(
        "Matrix client is not connected"
      );
    });
  });

  describe("Thread Management", () => {
    it("should create a thread by sending an initial message", async () => {
      await service.connect();
      const threadId = await service.createThread({
        channelId: "!test-room:example.com",
        name: "Job #42",
        message: "Starting job...",
      });

      expect(threadId).toBe("$event-id-123");
      expect(mockClient.sendMessage).toHaveBeenCalledWith("!test-room:example.com", {
        msgtype: "m.text",
        body: "[Job #42] Starting job...",
      });
    });

    it("should send a message to a thread with m.thread relation", async () => {
      await service.connect();
      await service.sendThreadMessage({
        threadId: "$root-event-id",
        channelId: "!test-room:example.com",
        content: "Step completed",
      });

      expect(mockClient.sendMessage).toHaveBeenCalledWith("!test-room:example.com", {
        msgtype: "m.text",
        body: "Step completed",
        "m.relates_to": {
          rel_type: "m.thread",
          event_id: "$root-event-id",
          is_falling_back: true,
          "m.in_reply_to": {
            event_id: "$root-event-id",
          },
        },
      });
    });

    it("should fall back to controlRoomId when channelId is empty", async () => {
      await service.connect();
      await service.sendThreadMessage({
        threadId: "$root-event-id",
        channelId: "",
        content: "Fallback message",
      });

      expect(mockClient.sendMessage).toHaveBeenCalledWith("!test-room:example.com", {
        msgtype: "m.text",
        body: "Fallback message",
        "m.relates_to": {
          rel_type: "m.thread",
          event_id: "$root-event-id",
          is_falling_back: true,
          "m.in_reply_to": {
            event_id: "$root-event-id",
          },
        },
      });
    });

    it("should throw error when creating thread without connection", async () => {
      await expect(
        service.createThread({
          channelId: "!room:example.com",
          name: "Test",
          message: "Test",
        })
      ).rejects.toThrow("Matrix client is not connected");
    });

    it("should throw error when sending thread message without connection", async () => {
      await expect(
        service.sendThreadMessage({
          threadId: "$event-id",
          channelId: "!room:example.com",
          content: "Test",
        })
      ).rejects.toThrow("Matrix client is not connected");
    });
  });

  describe("Command Parsing with shared CommandParserService", () => {
    it("should parse @mosaic fix #42 via shared parser", () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic fix #42",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).not.toBeNull();
      expect(command?.command).toBe("fix");
      expect(command?.args).toContain("#42");
    });

    it("should parse !mosaic fix #42 by normalizing to @mosaic for the shared parser", () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "!mosaic fix #42",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).not.toBeNull();
      expect(command?.command).toBe("fix");
      expect(command?.args).toContain("#42");
    });

    it("should parse @mosaic status command via shared parser", () => {
      const message: ChatMessage = {
        id: "msg-2",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic status job-123",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).not.toBeNull();
      expect(command?.command).toBe("status");
      expect(command?.args).toContain("job-123");
    });

    it("should parse @mosaic cancel command via shared parser", () => {
      const message: ChatMessage = {
        id: "msg-3",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic cancel job-456",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).not.toBeNull();
      expect(command?.command).toBe("cancel");
    });

    it("should parse @mosaic help command via shared parser", () => {
      const message: ChatMessage = {
        id: "msg-6",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic help",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).not.toBeNull();
      expect(command?.command).toBe("help");
    });

    it("should return null for non-command messages", () => {
      const message: ChatMessage = {
        id: "msg-7",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "Just a regular message",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).toBeNull();
    });

    it("should return null for messages without @mosaic or !mosaic mention", () => {
      const message: ChatMessage = {
        id: "msg-8",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "fix 42",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).toBeNull();
    });

    it("should return null for @mosaic mention without a command", () => {
      const message: ChatMessage = {
        id: "msg-11",
        channelId: "!room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic",
        timestamp: new Date(),
      };

      const command = service.parseCommand(message);

      expect(command).toBeNull();
    });
  });

  describe("Event-driven message reception", () => {
    it("should ignore messages from the bot itself", async () => {
      await service.connect();

      const parseCommandSpy = vi.spyOn(commandParser, "parseCommand");

      // Simulate a message from the bot
      expect(mockMessageCallbacks.length).toBeGreaterThan(0);
      const callback = mockMessageCallbacks[0];
      callback?.("!test-room:example.com", {
        event_id: "$msg-1",
        sender: "@mosaic-bot:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic fix #42",
        },
      });

      // Should not attempt to parse
      expect(parseCommandSpy).not.toHaveBeenCalled();
    });

    it("should ignore messages in unmapped rooms", async () => {
      // MatrixRoomService returns null for unknown rooms
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue(null);

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!unknown-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic fix #42",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should not dispatch to stitcher
      expect(stitcherService.dispatchJob).not.toHaveBeenCalled();
    });

    it("should process commands in the control room (fallback workspace)", async () => {
      // MatrixRoomService returns null, but room matches controlRoomId
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue(null);

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!test-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic help",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should send help message
      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("Available commands:"),
        })
      );
    });

    it("should process commands in rooms mapped via MatrixRoomService", async () => {
      // MatrixRoomService resolves the workspace
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue("mapped-workspace-id");

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!mapped-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic fix #42",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should dispatch with the mapped workspace ID
      expect(stitcherService.dispatchJob).toHaveBeenCalledWith(
        expect.objectContaining({
          workspaceId: "mapped-workspace-id",
        })
      );
    });

    it("should handle !mosaic prefix in incoming messages", async () => {
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue("test-workspace-id");

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!test-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "!mosaic help",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should send help message (normalized !mosaic -> @mosaic for parser)
      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("Available commands:"),
        })
      );
    });

    it("should send help text when user tries an unknown command", async () => {
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue("test-workspace-id");

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!test-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic invalidcommand",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should send error/help message (CommandParserService returns help text for unknown actions)
      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("Available commands"),
        })
      );
    });

    it("should ignore non-text messages", async () => {
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue("test-workspace-id");

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!test-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.image",
          body: "photo.jpg",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should not attempt any message sending
      expect(mockClient.sendMessage).not.toHaveBeenCalled();
    });
  });

  describe("Command Execution", () => {
    it("should forward fix command to stitcher and create a thread", async () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic fix 42",
        timestamp: new Date(),
      };

      await service.connect();
      await service.handleCommand({
        command: "fix",
        args: ["42"],
        message,
      });

      expect(stitcherService.dispatchJob).toHaveBeenCalledWith({
        workspaceId: "test-workspace-id",
        type: "code-task",
        priority: 10,
        metadata: {
          issueNumber: 42,
          command: "fix",
          channelId: "!test-room:example.com",
          threadId: "$event-id-123",
          authorId: "@user:example.com",
          authorName: "@user:example.com",
        },
      });
    });

    it("should handle fix with #-prefixed issue number", async () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic fix #42",
        timestamp: new Date(),
      };

      await service.connect();
      await service.handleCommand({
        command: "fix",
        args: ["#42"],
        message,
      });

      expect(stitcherService.dispatchJob).toHaveBeenCalledWith(
        expect.objectContaining({
          metadata: expect.objectContaining({
            issueNumber: 42,
          }),
        })
      );
    });

    it("should respond with help message", async () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic help",
        timestamp: new Date(),
      };

      await service.connect();
      await service.handleCommand({
        command: "help",
        args: [],
        message,
      });

      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("Available commands:"),
        })
      );
    });

    it("should include retry command in help output", async () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic help",
        timestamp: new Date(),
      };

      await service.connect();
      await service.handleCommand({
        command: "help",
        args: [],
        message,
      });

      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("retry"),
        })
      );
    });

    it("should send error for fix command without issue number", async () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic fix",
        timestamp: new Date(),
      };

      await service.connect();
      await service.handleCommand({
        command: "fix",
        args: [],
        message,
      });

      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("Usage:"),
        })
      );
    });

    it("should send error for fix command with non-numeric issue", async () => {
      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic fix abc",
        timestamp: new Date(),
      };

      await service.connect();
      await service.handleCommand({
        command: "fix",
        args: ["abc"],
        message,
      });

      expect(mockClient.sendMessage).toHaveBeenCalledWith(
        "!test-room:example.com",
        expect.objectContaining({
          body: expect.stringContaining("Invalid issue number"),
        })
      );
    });

    it("should dispatch fix command with workspace from MatrixRoomService", async () => {
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue("dynamic-workspace-id");

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!mapped-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic fix #99",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      expect(stitcherService.dispatchJob).toHaveBeenCalledWith(
        expect.objectContaining({
          workspaceId: "dynamic-workspace-id",
          metadata: expect.objectContaining({
            issueNumber: 99,
          }),
        })
      );
    });
  });

  describe("Configuration", () => {
    it("should throw error if MATRIX_HOMESERVER_URL is not set", async () => {
      delete process.env.MATRIX_HOMESERVER_URL;

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          MatrixService,
          CommandParserService,
          {
            provide: StitcherService,
            useValue: mockStitcherService,
          },
          {
            provide: MatrixRoomService,
            useValue: mockMatrixRoomService,
          },
        ],
      }).compile();

      const newService = module.get<MatrixService>(MatrixService);

      await expect(newService.connect()).rejects.toThrow("MATRIX_HOMESERVER_URL is required");

      // Restore for other tests
      process.env.MATRIX_HOMESERVER_URL = "https://matrix.example.com";
    });

    it("should throw error if MATRIX_ACCESS_TOKEN is not set", async () => {
      delete process.env.MATRIX_ACCESS_TOKEN;

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          MatrixService,
          CommandParserService,
          {
            provide: StitcherService,
            useValue: mockStitcherService,
          },
          {
            provide: MatrixRoomService,
            useValue: mockMatrixRoomService,
          },
        ],
      }).compile();

      const newService = module.get<MatrixService>(MatrixService);

      await expect(newService.connect()).rejects.toThrow("MATRIX_ACCESS_TOKEN is required");

      // Restore for other tests
      process.env.MATRIX_ACCESS_TOKEN = "test-access-token";
    });

    it("should throw error if MATRIX_BOT_USER_ID is not set", async () => {
      delete process.env.MATRIX_BOT_USER_ID;

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          MatrixService,
          CommandParserService,
          {
            provide: StitcherService,
            useValue: mockStitcherService,
          },
          {
            provide: MatrixRoomService,
            useValue: mockMatrixRoomService,
          },
        ],
      }).compile();

      const newService = module.get<MatrixService>(MatrixService);

      await expect(newService.connect()).rejects.toThrow("MATRIX_BOT_USER_ID is required");

      // Restore for other tests
      process.env.MATRIX_BOT_USER_ID = "@mosaic-bot:example.com";
    });

    it("should throw error if MATRIX_WORKSPACE_ID is not set", async () => {
      delete process.env.MATRIX_WORKSPACE_ID;

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          MatrixService,
          CommandParserService,
          {
            provide: StitcherService,
            useValue: mockStitcherService,
          },
          {
            provide: MatrixRoomService,
            useValue: mockMatrixRoomService,
          },
        ],
      }).compile();

      const newService = module.get<MatrixService>(MatrixService);

      await expect(newService.connect()).rejects.toThrow("MATRIX_WORKSPACE_ID is required");

      // Restore for other tests
      process.env.MATRIX_WORKSPACE_ID = "test-workspace-id";
    });

    it("should use configured workspace ID from environment", async () => {
      const testWorkspaceId = "configured-workspace-456";
      process.env.MATRIX_WORKSPACE_ID = testWorkspaceId;

      const module: TestingModule = await Test.createTestingModule({
        providers: [
          MatrixService,
          CommandParserService,
          {
            provide: StitcherService,
            useValue: mockStitcherService,
          },
          {
            provide: MatrixRoomService,
            useValue: mockMatrixRoomService,
          },
        ],
      }).compile();

      const newService = module.get<MatrixService>(MatrixService);

      const message: ChatMessage = {
        id: "msg-1",
        channelId: "!test-room:example.com",
        authorId: "@user:example.com",
        authorName: "@user:example.com",
        content: "@mosaic fix 42",
        timestamp: new Date(),
      };

      await newService.connect();
      await newService.handleCommand({
        command: "fix",
        args: ["42"],
        message,
      });

      expect(mockStitcherService.dispatchJob).toHaveBeenCalledWith(
        expect.objectContaining({
          workspaceId: testWorkspaceId,
        })
      );

      // Restore for other tests
      process.env.MATRIX_WORKSPACE_ID = "test-workspace-id";
    });
  });

  describe("Error Logging Security", () => {
    it("should sanitize sensitive data in error logs", async () => {
      const loggerErrorSpy = vi.spyOn(
        (service as Record<string, unknown>)["logger"] as { error: (...args: unknown[]) => void },
        "error"
      );

      await service.connect();

      // Trigger room.event handler with null event to exercise error path
      expect(mockEventCallbacks.length).toBeGreaterThan(0);
      mockEventCallbacks[0]?.("!room:example.com", null as unknown as Record<string, unknown>);

      // Verify error was logged
      expect(loggerErrorSpy).toHaveBeenCalled();

      // Get the logged error
      const loggedArgs = loggerErrorSpy.mock.calls[0];
      const loggedError = loggedArgs?.[1] as Record<string, unknown>;

      // Verify non-sensitive error info is preserved
      expect(loggedError).toBeDefined();
      expect((loggedError as { message: string }).message).toBe("Received null event from Matrix");
    });

    it("should not include access token in error output", () => {
      // Verify the access token is stored privately and not exposed
      const serviceAsRecord = service as unknown as Record<string, unknown>;
      // The accessToken should exist but should not appear in any public-facing method output
      expect(serviceAsRecord["accessToken"]).toBe("test-access-token");

      // Verify isConnected does not leak token
      const connected = service.isConnected();
      expect(String(connected)).not.toContain("test-access-token");
    });
  });

  describe("MatrixRoomService reverse lookup", () => {
    it("should call getWorkspaceForRoom when processing messages", async () => {
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue("resolved-workspace");

      await service.connect();

      const callback = mockMessageCallbacks[0];
      callback?.("!some-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic help",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      expect(matrixRoomService.getWorkspaceForRoom).toHaveBeenCalledWith("!some-room:example.com");
    });

    it("should fall back to control room workspace when MatrixRoomService returns null", async () => {
      mockMatrixRoomService.getWorkspaceForRoom.mockResolvedValue(null);

      await service.connect();

      const callback = mockMessageCallbacks[0];
      // Send to the control room (fallback path)
      callback?.("!test-room:example.com", {
        event_id: "$msg-1",
        sender: "@user:example.com",
        origin_server_ts: Date.now(),
        content: {
          msgtype: "m.text",
          body: "@mosaic fix #10",
        },
      });

      // Wait for async processing
      await new Promise((resolve) => setTimeout(resolve, 50));

      // Should dispatch with the env-configured workspace
      expect(stitcherService.dispatchJob).toHaveBeenCalledWith(
        expect.objectContaining({
          workspaceId: "test-workspace-id",
        })
      );
    });
  });
});
