import type { ChannelDirectoryEntry } from "openclaw/plugin-sdk/matrix";
import { resolveMatrixAuth } from "./matrix/client.js";

type MatrixUserResult = {
  user_id?: string;
  display_name?: string;
};

type MatrixUserDirectoryResponse = {
  results?: MatrixUserResult[];
};

type MatrixJoinedRoomsResponse = {
  joined_rooms?: string[];
};

type MatrixRoomNameState = {
  name?: string;
};

type MatrixAliasLookup = {
  room_id?: string;
};

type MatrixDirectoryLiveParams = {
  cfg: unknown;
  accountId?: string | null;
  query?: string | null;
  limit?: number | null;
};

type MatrixResolvedAuth = Awaited<ReturnType<typeof resolveMatrixAuth>>;

async function fetchMatrixJson<T>(params: {
  homeserver: string;
  path: string;
  accessToken: string;
  method?: "GET" | "POST";
  body?: unknown;
}): Promise<T> {
  const res = await fetch(`${params.homeserver}${params.path}`, {
    method: params.method ?? "GET",
    headers: {
      Authorization: `Bearer ${params.accessToken}`,
      "Content-Type": "application/json",
    },
    body: params.body ? JSON.stringify(params.body) : undefined,
  });
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`Matrix API ${params.path} failed (${res.status}): ${text || "unknown error"}`);
  }
  return (await res.json()) as T;
}

function normalizeQuery(value?: string | null): string {
  return value?.trim().toLowerCase() ?? "";
}

function resolveMatrixDirectoryLimit(limit?: number | null): number {
  return typeof limit === "number" && limit > 0 ? limit : 20;
}

async function resolveMatrixDirectoryContext(
  params: MatrixDirectoryLiveParams,
): Promise<{ query: string; auth: MatrixResolvedAuth } | null> {
  const query = normalizeQuery(params.query);
  if (!query) {
    return null;
  }
  const auth = await resolveMatrixAuth({ cfg: params.cfg as never, accountId: params.accountId });
  return { query, auth };
}

function createGroupDirectoryEntry(params: {
  id: string;
  name: string;
  handle?: string;
}): ChannelDirectoryEntry {
  return {
    kind: "group",
    id: params.id,
    name: params.name,
    handle: params.handle,
  } satisfies ChannelDirectoryEntry;
}

export async function listMatrixDirectoryPeersLive(
  params: MatrixDirectoryLiveParams,
): Promise<ChannelDirectoryEntry[]> {
  const context = await resolveMatrixDirectoryContext(params);
  if (!context) {
    return [];
  }
  const { query, auth } = context;
  const res = await fetchMatrixJson<MatrixUserDirectoryResponse>({
    homeserver: auth.homeserver,
    accessToken: auth.accessToken,
    path: "/_matrix/client/v3/user_directory/search",
    method: "POST",
    body: {
      search_term: query,
      limit: resolveMatrixDirectoryLimit(params.limit),
    },
  });
  const results = res.results ?? [];
  return results
    .map((entry) => {
      const userId = entry.user_id?.trim();
      if (!userId) {
        return null;
      }
      return {
        kind: "user",
        id: userId,
        name: entry.display_name?.trim() || undefined,
        handle: entry.display_name ? `@${entry.display_name.trim()}` : undefined,
        raw: entry,
      } satisfies ChannelDirectoryEntry;
    })
    .filter(Boolean) as ChannelDirectoryEntry[];
}

async function resolveMatrixRoomAlias(
  homeserver: string,
  accessToken: string,
  alias: string,
): Promise<string | null> {
  try {
    const res = await fetchMatrixJson<MatrixAliasLookup>({
      homeserver,
      accessToken,
      path: `/_matrix/client/v3/directory/room/${encodeURIComponent(alias)}`,
    });
    return res.room_id?.trim() || null;
  } catch {
    return null;
  }
}

async function fetchMatrixRoomName(
  homeserver: string,
  accessToken: string,
  roomId: string,
): Promise<string | null> {
  try {
    const res = await fetchMatrixJson<MatrixRoomNameState>({
      homeserver,
      accessToken,
      path: `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/state/m.room.name`,
    });
    return res.name?.trim() || null;
  } catch {
    return null;
  }
}

export async function listMatrixDirectoryGroupsLive(
  params: MatrixDirectoryLiveParams,
): Promise<ChannelDirectoryEntry[]> {
  const context = await resolveMatrixDirectoryContext(params);
  if (!context) {
    return [];
  }
  const { query, auth } = context;
  const limit = resolveMatrixDirectoryLimit(params.limit);

  if (query.startsWith("#")) {
    const roomId = await resolveMatrixRoomAlias(auth.homeserver, auth.accessToken, query);
    if (!roomId) {
      return [];
    }
    return [createGroupDirectoryEntry({ id: roomId, name: query, handle: query })];
  }

  if (query.startsWith("!")) {
    const originalId = params.query?.trim() ?? query;
    return [createGroupDirectoryEntry({ id: originalId, name: originalId })];
  }

  const joined = await fetchMatrixJson<MatrixJoinedRoomsResponse>({
    homeserver: auth.homeserver,
    accessToken: auth.accessToken,
    path: "/_matrix/client/v3/joined_rooms",
  });
  const rooms = joined.joined_rooms ?? [];
  const results: ChannelDirectoryEntry[] = [];

  for (const roomId of rooms) {
    const name = await fetchMatrixRoomName(auth.homeserver, auth.accessToken, roomId);
    if (!name) {
      continue;
    }
    if (!name.toLowerCase().includes(query)) {
      continue;
    }
    results.push({
      kind: "group",
      id: roomId,
      name,
      handle: `#${name}`,
    });
    if (results.length >= limit) {
      break;
    }
  }

  return results;
}
