import { Test, TestingModule } from "@nestjs/testing";
import { MatrixRoomService } from "./matrix-room.service";
import { MatrixService } from "./matrix.service";
import { PrismaService } from "../../prisma/prisma.service";
import { vi, describe, it, expect, beforeEach } from "vitest";

// Mock matrix-bot-sdk to avoid native module import errors
vi.mock("matrix-bot-sdk", () => {
  return {
    MatrixClient: class MockMatrixClient {},
    SimpleFsStorageProvider: class MockStorageProvider {
      constructor(_filename: string) {
        // No-op for testing
      }
    },
    AutojoinRoomsMixin: {
      setupOnClient: vi.fn(),
    },
  };
});

describe("MatrixRoomService", () => {
  let service: MatrixRoomService;

  const mockCreateRoom = vi.fn().mockResolvedValue("!new-room:example.com");

  const mockMatrixClient = {
    createRoom: mockCreateRoom,
  };

  const mockMatrixService = {
    isConnected: vi.fn().mockReturnValue(true),
    getClient: vi.fn().mockReturnValue(mockMatrixClient),
  };

  const mockPrismaService = {
    workspace: {
      findUnique: vi.fn(),
      findFirst: vi.fn(),
      update: vi.fn(),
    },
  };

  beforeEach(async () => {
    process.env.MATRIX_SERVER_NAME = "example.com";

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MatrixRoomService,
        {
          provide: PrismaService,
          useValue: mockPrismaService,
        },
        {
          provide: MatrixService,
          useValue: mockMatrixService,
        },
      ],
    }).compile();

    service = module.get<MatrixRoomService>(MatrixRoomService);

    vi.clearAllMocks();
    // Restore defaults after clearing
    mockMatrixService.isConnected.mockReturnValue(true);
    mockCreateRoom.mockResolvedValue("!new-room:example.com");
    mockPrismaService.workspace.update.mockResolvedValue({});
  });

  describe("provisionRoom", () => {
    it("should create a Matrix room and store the mapping", async () => {
      const roomId = await service.provisionRoom(
        "workspace-uuid-1",
        "My Workspace",
        "my-workspace"
      );

      expect(roomId).toBe("!new-room:example.com");

      expect(mockCreateRoom).toHaveBeenCalledWith({
        name: "Mosaic: My Workspace",
        room_alias_name: "mosaic-my-workspace",
        topic: "Mosaic workspace: My Workspace",
        preset: "private_chat",
        visibility: "private",
      });

      expect(mockPrismaService.workspace.update).toHaveBeenCalledWith({
        where: { id: "workspace-uuid-1" },
        data: { matrixRoomId: "!new-room:example.com" },
      });
    });

    it("should return null when Matrix is not configured (no MatrixService)", async () => {
      // Create a service without MatrixService
      const module: TestingModule = await Test.createTestingModule({
        providers: [
          MatrixRoomService,
          {
            provide: PrismaService,
            useValue: mockPrismaService,
          },
        ],
      }).compile();

      const serviceWithoutMatrix = module.get<MatrixRoomService>(MatrixRoomService);

      const roomId = await serviceWithoutMatrix.provisionRoom(
        "workspace-uuid-1",
        "My Workspace",
        "my-workspace"
      );

      expect(roomId).toBeNull();
      expect(mockCreateRoom).not.toHaveBeenCalled();
      expect(mockPrismaService.workspace.update).not.toHaveBeenCalled();
    });

    it("should return null when Matrix is not connected", async () => {
      mockMatrixService.isConnected.mockReturnValue(false);

      const roomId = await service.provisionRoom(
        "workspace-uuid-1",
        "My Workspace",
        "my-workspace"
      );

      expect(roomId).toBeNull();
      expect(mockCreateRoom).not.toHaveBeenCalled();
    });
  });

  describe("getRoomForWorkspace", () => {
    it("should return the room ID for a mapped workspace", async () => {
      mockPrismaService.workspace.findUnique.mockResolvedValue({
        matrixRoomId: "!mapped-room:example.com",
      });

      const roomId = await service.getRoomForWorkspace("workspace-uuid-1");

      expect(roomId).toBe("!mapped-room:example.com");
      expect(mockPrismaService.workspace.findUnique).toHaveBeenCalledWith({
        where: { id: "workspace-uuid-1" },
        select: { matrixRoomId: true },
      });
    });

    it("should return null for an unmapped workspace", async () => {
      mockPrismaService.workspace.findUnique.mockResolvedValue({
        matrixRoomId: null,
      });

      const roomId = await service.getRoomForWorkspace("workspace-uuid-2");

      expect(roomId).toBeNull();
    });

    it("should return null for a non-existent workspace", async () => {
      mockPrismaService.workspace.findUnique.mockResolvedValue(null);

      const roomId = await service.getRoomForWorkspace("non-existent-uuid");

      expect(roomId).toBeNull();
    });
  });

  describe("getWorkspaceForRoom", () => {
    it("should return the workspace ID for a mapped room", async () => {
      mockPrismaService.workspace.findFirst.mockResolvedValue({
        id: "workspace-uuid-1",
      });

      const workspaceId = await service.getWorkspaceForRoom("!mapped-room:example.com");

      expect(workspaceId).toBe("workspace-uuid-1");
      expect(mockPrismaService.workspace.findFirst).toHaveBeenCalledWith({
        where: { matrixRoomId: "!mapped-room:example.com" },
        select: { id: true },
      });
    });

    it("should return null for an unmapped room", async () => {
      mockPrismaService.workspace.findFirst.mockResolvedValue(null);

      const workspaceId = await service.getWorkspaceForRoom("!unknown-room:example.com");

      expect(workspaceId).toBeNull();
    });
  });

  describe("linkWorkspaceToRoom", () => {
    it("should store the room mapping in the workspace", async () => {
      await service.linkWorkspaceToRoom("workspace-uuid-1", "!existing-room:example.com");

      expect(mockPrismaService.workspace.update).toHaveBeenCalledWith({
        where: { id: "workspace-uuid-1" },
        data: { matrixRoomId: "!existing-room:example.com" },
      });
    });
  });

  describe("unlinkWorkspace", () => {
    it("should remove the room mapping from the workspace", async () => {
      await service.unlinkWorkspace("workspace-uuid-1");

      expect(mockPrismaService.workspace.update).toHaveBeenCalledWith({
        where: { id: "workspace-uuid-1" },
        data: { matrixRoomId: null },
      });
    });
  });
});
