import crypto from 'crypto';
import { eq, or, and, inArray } from 'drizzle-orm';
import { getDb, schema } from '../db/index.js';

export interface DeletionBroadcastTargets {
  /** Space IDs the user is a member of (for member_left broadcasts) */
  memberSpaceIds: string[];
  /** All user IDs who may have cached data for this user (for user_updated broadcast) */
  targetUserIds: Set<string>;
}

/**
 * Collect the set of user IDs who should receive a user_updated broadcast
 * for a given user. Includes: space co-members, DM co-members, and friends.
 * Does NOT include the user themselves — callers add self if needed.
 */
export function collectProfileBroadcastTargetIds(uid: string): Set<string> {
  const db = getDb();
  const targetUserIds = new Set<string>();

  // 1. All users who share a space with this user
  const memberSpaceIds = db.select({ spaceId: schema.spaceMembers.spaceId })
    .from(schema.spaceMembers)
    .where(eq(schema.spaceMembers.userId, uid))
    .all()
    .map(m => m.spaceId);

  if (memberSpaceIds.length > 0) {
    const coMembers = db.select({ userId: schema.spaceMembers.userId })
      .from(schema.spaceMembers)
      .where(inArray(schema.spaceMembers.spaceId, memberSpaceIds))
      .all();
    for (const m of coMembers) targetUserIds.add(m.userId);
  }

  // 2. All DM co-members
  const dmMemberships = db.select({ dmChannelId: schema.dmMembers.dmChannelId })
    .from(schema.dmMembers)
    .where(eq(schema.dmMembers.userId, uid))
    .all();
  if (dmMemberships.length > 0) {
    const dmChannelIds = dmMemberships.map(d => d.dmChannelId);
    const dmCoMembers = db.select({ userId: schema.dmMembers.userId })
      .from(schema.dmMembers)
      .where(inArray(schema.dmMembers.dmChannelId, dmChannelIds))
      .all();
    for (const m of dmCoMembers) targetUserIds.add(m.userId);
  }

  // 3. All friends
  const friendRows = db.select().from(schema.friends)
    .where(or(eq(schema.friends.userId, uid), eq(schema.friends.friendId, uid)))
    .all();
  for (const fr of friendRows) {
    targetUserIds.add(fr.userId === uid ? fr.friendId : fr.userId);
  }

  // Remove the target user themselves — callers decide whether to include self
  targetUserIds.delete(uid);

  return targetUserIds;
}

/**
 * Collect broadcast targets for a user deletion. Must be called BEFORE
 * tombstoneUser() since that transaction deletes the membership/friend/DM rows.
 *
 * Collects: space co-members, DM co-members, and friends.
 * sendToUser() no-ops for offline users, so no online-filtering needed.
 */
export function collectDeletionBroadcastTargets(uid: string): DeletionBroadcastTargets {
  const db = getDb();

  // Collect space IDs (needed for member_left broadcasts — not part of the shared helper)
  const memberSpaceIds = db.select({ spaceId: schema.spaceMembers.spaceId })
    .from(schema.spaceMembers)
    .where(eq(schema.spaceMembers.userId, uid))
    .all()
    .map(m => m.spaceId);

  const targetUserIds = collectProfileBroadcastTargetIds(uid);

  return { memberSpaceIds, targetUserIds };
}

export interface TombstoneOptions {
  /** When false, skip space message/reaction deletion (soft-delete mode). Orphaned DM cleanup always runs. Default: true */
  purgeContent?: boolean;
}

/**
 * Tombstone a user account: removes them from all spaces, DMs, friends,
 * roles, reactions, folders, bans, voice restrictions, channel overrides,
 * transfers group DM ownership, cleans up orphaned DMs, and marks the
 * user row as deleted.
 *
 * Returns a list of filenames to delete from disk (avatar, banner,
 * orphaned DM attachments). The caller is responsible for disk cleanup
 * and WebSocket disconnection after calling this.
 */
export function tombstoneUser(uid: string, options?: TombstoneOptions): string[] {
  const db = getDb();

  const user = db.select().from(schema.users).where(eq(schema.users.id, uid)).get();
  if (!user) return [];

  const filesToDelete: string[] = [];
  if (user.avatar) filesToDelete.push(user.avatar);
  if (user.banner) filesToDelete.push(user.banner);

  const purge = options?.purgeContent !== false; // default true

  // Find group DMs this user owns so we can transfer ownership
  const ownedGroupDms = db.select({ id: schema.dmChannels.id })
    .from(schema.dmChannels)
    .where(eq(schema.dmChannels.ownerId, uid))
    .all();

  db.transaction((tx) => {
    // Remove from spaces, roles, friends, DMs, read states, reactions, folders
    tx.delete(schema.spaceMembers).where(eq(schema.spaceMembers.userId, uid)).run();
    tx.delete(schema.memberRoles).where(eq(schema.memberRoles.userId, uid)).run();
    tx.delete(schema.friends).where(or(eq(schema.friends.userId, uid), eq(schema.friends.friendId, uid))).run();
    tx.delete(schema.friendRequests).where(or(eq(schema.friendRequests.fromId, uid), eq(schema.friendRequests.toId, uid))).run();
    tx.delete(schema.dmMembers).where(eq(schema.dmMembers.userId, uid)).run();
    tx.delete(schema.readStates).where(eq(schema.readStates.userId, uid)).run();
    if (purge) {
      tx.delete(schema.reactions).where(eq(schema.reactions.userId, uid)).run();
      tx.delete(schema.dmReactions).where(eq(schema.dmReactions.userId, uid)).run();
    }
    tx.delete(schema.spaceFolders).where(eq(schema.spaceFolders.userId, uid)).run();

    // Conditional deletes for tables that may reference userId
    try { tx.delete(schema.bans).where(eq(schema.bans.userId, uid)).run(); } catch { /* table may not exist */ }
    try { tx.delete(schema.joinRequests).where(eq(schema.joinRequests.userId, uid)).run(); } catch { /* table may not exist */ }
    try { tx.delete(schema.voiceRestrictions).where(eq(schema.voiceRestrictions.userId, uid)).run(); } catch { /* table may not exist */ }

    // Nullify moderator references pointing to this user
    try {
      tx.update(schema.bans).set({ bannedBy: null }).where(eq(schema.bans.bannedBy, uid)).run();
    } catch { /* table may not exist */ }
    try {
      tx.update(schema.voiceRestrictions).set({ moderatorId: null }).where(eq(schema.voiceRestrictions.moderatorId, uid)).run();
    } catch { /* table may not exist */ }
    try {
      tx.update(schema.joinRequests).set({ decidedBy: null }).where(eq(schema.joinRequests.decidedBy, uid)).run();
    } catch { /* table may not exist */ }

    // Remove member-type channel overrides for this user
    tx.delete(schema.channelOverrides).where(
      and(eq(schema.channelOverrides.targetType, 'member'), eq(schema.channelOverrides.targetId, uid))
    ).run();

    // Transfer ownership of group DMs to the next remaining member
    for (const { id: dmId } of ownedGroupDms) {
      const nextMember = tx.select({ userId: schema.dmMembers.userId })
        .from(schema.dmMembers)
        .where(eq(schema.dmMembers.dmChannelId, dmId))
        .limit(1)
        .get();
      if (nextMember) {
        tx.update(schema.dmChannels)
          .set({ ownerId: nextMember.userId })
          .where(eq(schema.dmChannels.id, dmId))
          .run();
      }
    }

    // Clean up orphaned DM channels (zero members after our removal) — always runs,
    // orphaned channels are unreachable garbage regardless of purge mode
    const orphanedDmIds = tx.select({ id: schema.dmChannels.id })
      .from(schema.dmChannels)
      .all()
      .filter(dc => {
        const memberCount = tx.select({ id: schema.dmMembers.dmChannelId })
          .from(schema.dmMembers)
          .where(eq(schema.dmMembers.dmChannelId, dc.id))
          .all()
          .length;
        return memberCount === 0;
      })
      .map(dc => dc.id);

    for (const dmId of orphanedDmIds) {
      const msgIds = tx.select({ id: schema.dmMessages.id })
        .from(schema.dmMessages)
        .where(eq(schema.dmMessages.dmChannelId, dmId))
        .all()
        .map(m => m.id);

      if (msgIds.length > 0) {
        const dmAttachments = tx.select({ filename: schema.attachments.filename })
          .from(schema.attachments)
          .where(inArray(schema.attachments.dmMessageId, msgIds))
          .all();
        for (const att of dmAttachments) filesToDelete.push(att.filename);

        tx.delete(schema.attachments).where(inArray(schema.attachments.dmMessageId, msgIds)).run();
        tx.delete(schema.dmReactions).where(inArray(schema.dmReactions.dmMessageId, msgIds)).run();
      }
      tx.delete(schema.dmChannels).where(eq(schema.dmChannels.id, dmId)).run();
    }

    // Purge mode: delete the user's space messages and their attachments
    if (purge) {
      const userMessageIds = tx.select({ id: schema.messages.id })
        .from(schema.messages)
        .where(eq(schema.messages.userId, uid))
        .all()
        .map(m => m.id);

      if (userMessageIds.length > 0) {
        // Collect attachment files for disk cleanup
        const msgAttachments = tx.select({ filename: schema.attachments.filename })
          .from(schema.attachments)
          .where(inArray(schema.attachments.messageId, userMessageIds))
          .all();
        for (const att of msgAttachments) filesToDelete.push(att.filename);

        // Delete attachments, embeds, then messages (FK order)
        tx.delete(schema.attachments).where(inArray(schema.attachments.messageId, userMessageIds)).run();
        tx.delete(schema.embeds).where(inArray(schema.embeds.messageId, userMessageIds)).run();
        tx.delete(schema.messages).where(eq(schema.messages.userId, uid)).run();
      }
    }

    // Tombstone user row — rename username to free it for reuse
    tx.update(schema.users).set({
      username: `!deleted:${uid}`,
      passwordHash: crypto.randomBytes(32).toString('hex'),
      displayName: null,
      avatar: null,
      banner: null,
      bio: null,
      customStatus: null,
      accentColor: null,
      avatarColor: null,
      replicatedInstances: '[]',
      isDeleted: 1,
      status: 'offline',
      isAdmin: 0,
    }).where(eq(schema.users.id, uid)).run();
  });

  return filesToDelete;
}
