import { Injectable, Logger, Optional, Inject } from "@nestjs/common";
import { PrismaService } from "../../prisma/prisma.service";
import { MatrixService } from "./matrix.service";
import type { MatrixClient, RoomCreateOptions } from "matrix-bot-sdk";

/**
 * MatrixRoomService - Workspace-to-Matrix-Room mapping and provisioning
 *
 * Responsibilities:
 * - Provision Matrix rooms for Mosaic workspaces
 * - Map workspaces to Matrix room IDs
 * - Link/unlink existing rooms to workspaces
 *
 * Room provisioning creates a private Matrix room with:
 * - Name: "Mosaic: {workspace_name}"
 * - Alias: #mosaic-{workspace_slug}:{server_name}
 * - Room ID stored in workspace.matrixRoomId
 */
@Injectable()
export class MatrixRoomService {
  private readonly logger = new Logger(MatrixRoomService.name);

  constructor(
    private readonly prisma: PrismaService,
    @Optional() @Inject(MatrixService) private readonly matrixService: MatrixService | null
  ) {}

  /**
   * Provision a Matrix room for a workspace and store the mapping.
   *
   * @param workspaceId - The workspace UUID
   * @param workspaceName - Human-readable workspace name
   * @param workspaceSlug - URL-safe workspace identifier for the room alias
   * @returns The Matrix room ID, or null if Matrix is not configured
   */
  async provisionRoom(
    workspaceId: string,
    workspaceName: string,
    workspaceSlug: string
  ): Promise<string | null> {
    if (!this.matrixService?.isConnected()) {
      this.logger.warn("Matrix is not configured or not connected; skipping room provisioning");
      return null;
    }

    const client = this.getMatrixClient();
    if (!client) {
      this.logger.warn("Matrix client is not available; skipping room provisioning");
      return null;
    }

    const roomOptions: RoomCreateOptions = {
      name: `Mosaic: ${workspaceName}`,
      room_alias_name: `mosaic-${workspaceSlug}`,
      topic: `Mosaic workspace: ${workspaceName}`,
      preset: "private_chat",
      visibility: "private",
    };

    this.logger.log(
      `Provisioning Matrix room for workspace "${workspaceName}" (${workspaceId})...`
    );

    const roomId = await client.createRoom(roomOptions);

    // Store the room mapping
    try {
      await this.prisma.workspace.update({
        where: { id: workspaceId },
        data: { matrixRoomId: roomId },
      });
    } catch (dbError: unknown) {
      this.logger.error(
        `Failed to store room mapping for workspace ${workspaceId}, room ${roomId} may be orphaned: ${dbError instanceof Error ? dbError.message : "unknown"}`
      );
      throw dbError;
    }

    this.logger.log(`Matrix room ${roomId} provisioned and linked to workspace ${workspaceId}`);

    return roomId;
  }

  /**
   * Look up the Matrix room ID mapped to a workspace.
   *
   * @param workspaceId - The workspace UUID
   * @returns The Matrix room ID, or null if no room is mapped
   */
  async getRoomForWorkspace(workspaceId: string): Promise<string | null> {
    const workspace = await this.prisma.workspace.findUnique({
      where: { id: workspaceId },
      select: { matrixRoomId: true },
    });

    if (!workspace) {
      return null;
    }
    return workspace.matrixRoomId ?? null;
  }

  /**
   * Reverse lookup: find the workspace that owns a given Matrix room.
   *
   * @param roomId - The Matrix room ID (e.g. "!abc:example.com")
   * @returns The workspace ID, or null if the room is not mapped to any workspace
   */
  async getWorkspaceForRoom(roomId: string): Promise<string | null> {
    const workspace = await this.prisma.workspace.findFirst({
      where: { matrixRoomId: roomId },
      select: { id: true },
    });

    return workspace?.id ?? null;
  }

  /**
   * Manually link an existing Matrix room to a workspace.
   *
   * @param workspaceId - The workspace UUID
   * @param roomId - The Matrix room ID to link
   */
  async linkWorkspaceToRoom(workspaceId: string, roomId: string): Promise<void> {
    await this.prisma.workspace.update({
      where: { id: workspaceId },
      data: { matrixRoomId: roomId },
    });

    this.logger.log(`Linked workspace ${workspaceId} to Matrix room ${roomId}`);
  }

  /**
   * Remove the Matrix room mapping from a workspace.
   *
   * @param workspaceId - The workspace UUID
   */
  async unlinkWorkspace(workspaceId: string): Promise<void> {
    await this.prisma.workspace.update({
      where: { id: workspaceId },
      data: { matrixRoomId: null },
    });

    this.logger.log(`Unlinked Matrix room from workspace ${workspaceId}`);
  }

  /**
   * Access the underlying MatrixClient from the MatrixService
   * via the public getClient() accessor.
   */
  private getMatrixClient(): MatrixClient | null {
    if (!this.matrixService) return null;
    return this.matrixService.getClient();
  }
}
