import type { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
import { MessageEvent, StateEvent } from '@cinny/types/matrix/room';
import type { IPowerLevels } from '@cinny/app/hooks/usePowerLevels';

type AnyFn = (...args: never[]) => unknown;

function fn<T extends AnyFn>(impl?: T): T {
  return (impl ?? ((() => undefined) as T)) as T;
}

export const MOCK_USER_ID = '@alice:example.org';
export const MOCK_OTHER_USER_ID = '@bob:example.org';
export const MOCK_ROOM_ID = '!room:example.org';
export const MOCK_EVENT_ID = '$event:example.org';

/** Default power levels — user can send messages / invite. */
export const MOCK_POWER_LEVELS: IPowerLevels = {
  users_default: 0,
  state_default: 50,
  events_default: 0,
  invite: 0,
  redact: 50,
  kick: 50,
  ban: 50,
  historical: 0,
  events: {
    'm.room.message': 0,
    'm.reaction': 0,
  },
  users: {
    [MOCK_USER_ID]: 100,
  },
  notifications: {
    room: 50,
  },
};

function mockStateEvent(type: string, content: Record<string, unknown>, stateKey = ''): MatrixEvent {
  return {
    getId: () => `$${type}`,
    getSender: () => MOCK_USER_ID,
    getTs: () => Date.now(),
    getType: () => type,
    getContent: () => content,
    getWireContent: () => content,
    isRedacted: () => false,
    getStateKey: () => stateKey,
    isEncrypted: () => false,
    getRoomId: () => MOCK_ROOM_ID,
    event: { sender: MOCK_USER_ID },
  } as unknown as MatrixEvent;
}

/** Lightweight MatrixEvent stand-in for UI previews. */
export function createMockMatrixEvent(
  overrides: Partial<{
    id: string;
    sender: string;
    body: string;
    ts: number;
    type: string;
    redacted: boolean;
    content: Record<string, unknown>;
  }> = {}
): MatrixEvent {
  const id = overrides.id ?? MOCK_EVENT_ID;
  const sender = overrides.sender ?? MOCK_OTHER_USER_ID;
  const content = {
    body: overrides.body ?? 'Hello from the playground preview.',
    msgtype: 'm.text',
    ...(overrides.content ?? {}),
  };
  const ts = overrides.ts ?? Date.now() - 60_000;
  const type = overrides.type ?? MessageEvent.RoomMessage;
  const redacted = overrides.redacted ?? false;

  return {
    getId: () => id,
    getSender: () => sender,
    getTs: () => ts,
    getType: () => type,
    getContent: () => (redacted ? {} : content),
    getWireContent: () => content,
    isRedacted: () => redacted,
    getStateKey: () => undefined,
    isEncrypted: () => false,
    threadRootId: undefined,
    getRoomId: () => MOCK_ROOM_ID,
  } as unknown as MatrixEvent;
}

/** Lightweight Room stand-in. */
export function createMockRoom(
  overrides: Partial<{
    roomId: string;
    name: string;
    client: MatrixClient;
    members: Record<string, { displayName?: string; avatarMxc?: string }>;
    powerLevels: IPowerLevels;
  }> = {}
): Room {
  const roomId = overrides.roomId ?? MOCK_ROOM_ID;
  const members = {
    [MOCK_USER_ID]: { displayName: 'Alice' },
    [MOCK_OTHER_USER_ID]: { displayName: 'Bob' },
    ...(overrides.members ?? {}),
  };
  const powerLevels = overrides.powerLevels ?? MOCK_POWER_LEVELS;

  const stateByType: Record<string, MatrixEvent> = {
    [StateEvent.RoomPowerLevels]: mockStateEvent(
      StateEvent.RoomPowerLevels,
      powerLevels as unknown as Record<string, unknown>
    ),
    [StateEvent.RoomCreate]: mockStateEvent(StateEvent.RoomCreate, {
      room_version: '11',
      creator: MOCK_USER_ID,
    }),
    [StateEvent.RoomPinnedEvents]: mockStateEvent(StateEvent.RoomPinnedEvents, { pinned: [] }),
  };

  const getStateEvents = ((type: string, stateKey?: string) => {
    const ev = stateByType[type];
    if (stateKey !== undefined) return ev ?? null;
    return ev ? [ev] : [];
  }) as Room['currentState']['getStateEvents'];

  const liveState = {
    getStateEvents,
  };

  const client = overrides.client ?? ({
    on: fn(() => undefined),
    removeListener: fn(() => undefined),
    once: fn(() => undefined),
    off: fn(() => undefined),
  } as unknown as MatrixClient);

  return {
    roomId,
    name: overrides.name ?? 'Preview Room',
    client,
    getMyMembership: () => 'join',
    getMember: (userId: string) => {
      const m = members[userId];
      if (!m) return null;
      return {
        userId,
        name: m.displayName ?? userId,
        rawDisplayName: m.displayName ?? userId,
        getAvatarUrl: () => null,
        getMxcAvatarUrl: () => m.avatarMxc ?? null,
      };
    },
    getCanonicalAlias: () => null,
    getAltAliases: () => [],
    getTimelineForEvent: () => null,
    getLiveTimeline: () => ({
      getTimelineSet: () => ({ relations: { getChildEventsForEvent: () => null } }),
      getEvents: () => [],
      getState: () => liveState,
    }),
    currentState: {
      getStateEvents,
    },
  } as unknown as Room;
}

/** Lightweight MatrixClient stand-in used by MatrixClientProvider. */
export function createMockMatrixClient(
  overrides: Partial<{ userId: string }> = {}
): MatrixClient {
  const userId = overrides.userId ?? MOCK_USER_ID;
  const users = new Map<
    string,
    {
      userId: string;
      presence: string;
      presenceStatusMsg?: string;
      currentlyActive: boolean;
      getLastActiveTs: () => number;
      on: AnyFn;
      removeListener: AnyFn;
      once: AnyFn;
      off: AnyFn;
    }
  >();

  const ensureUser = (id: string) => {
    if (!users.has(id)) {
      users.set(id, {
        userId: id,
        presence: 'online',
        currentlyActive: true,
        getLastActiveTs: () => Date.now(),
        on: fn(() => undefined),
        removeListener: fn(() => undefined),
        once: fn(() => undefined),
        off: fn(() => undefined),
      });
    }
    return users.get(id)!;
  };
  ensureUser(userId);
  ensureUser(MOCK_OTHER_USER_ID);

  const client = {
    getUserId: () => userId,
    getSafeUserId: () => userId,
    getDomain: () => 'example.org',
    getUser: (id: string) => ensureUser(id),
    getRoom: (id: string | undefined) =>
      id ? createMockRoom({ roomId: id, client: client as MatrixClient }) : null,
    getRooms: () => [createMockRoom({ client: client as MatrixClient })],
    isRoomEncrypted: () => false,
    getAccountData: () => undefined,
    sendEvent: fn(async () => ({ event_id: '$sent' })),
    sendStateEvent: fn(async () => ({ event_id: '$state' })),
    redactEvent: fn(async () => ({ event_id: '$redact' })),
    reportEvent: fn(async () => ({})),
    downloadKeys: fn(async () => ({})),
    mxcUrlToHttp: (mxc: string) => mxc,
    on: fn(() => undefined),
    removeListener: fn(() => undefined),
    once: fn(() => undefined),
  } as unknown as MatrixClient;

  return client;
}

const defaultClient = createMockMatrixClient();

export const mocks = {
  userId: MOCK_USER_ID,
  otherUserId: MOCK_OTHER_USER_ID,
  roomId: MOCK_ROOM_ID,
  eventId: MOCK_EVENT_ID,
  powerLevels: MOCK_POWER_LEVELS,
  client: defaultClient,
  room: createMockRoom({ client: defaultClient }),
  event: createMockMatrixEvent(),
  createClient: createMockMatrixClient,
  createRoom: createMockRoom,
  createEvent: createMockMatrixEvent,
};
