import {
  and,
  eq,
  notExists,
  or,
  sql,
  type AnyColumn,
} from 'drizzle-orm'

import {db, schema} from '../db/client'
import {friendshipPairWhere} from './friends'

// a block edge in either direction between a and b
export const blockPairWhere = (a: string, b: string) =>
  or(
    and(
      eq(schema.userBlocks.blockerId, a),
      eq(schema.userBlocks.blockedId, b),
    ),
    and(
      eq(schema.userBlocks.blockerId, b),
      eq(schema.userBlocks.blockedId, a),
    ),
  )

// a block exists either way — the guard for any mutual interaction (friend
// request, invite, dm)
export const isBlockedBetween = async (
  a: string,
  b: string,
): Promise<boolean> => {
  const [row] = await db
    .select({id: schema.userBlocks.id})
    .from(schema.userBlocks)
    .where(blockPairWhere(a, b))
    .limit(1)
  return Boolean(row)
}

export type BlockState = {blocking: boolean; blockedBy: boolean}

// directional view for a profile: did the viewer block the other, and/or the
// other block the viewer
export const blockStateBetween = async (
  viewerId: string,
  otherId: string,
): Promise<BlockState> => {
  const rows = await db
    .select({blockerId: schema.userBlocks.blockerId})
    .from(schema.userBlocks)
    .where(blockPairWhere(viewerId, otherId))
  return {
    blocking: rows.some((r) => r.blockerId === viewerId),
    blockedBy: rows.some((r) => r.blockerId === otherId),
  }
}

// every user id with a block edge either direction to the viewer — the set to
// suppress from feeds, inboxes and shared surfaces
export const listBlockedUserIds = async (
  viewerId: string,
): Promise<string[]> => {
  const rows = await db
    .select({
      blockerId: schema.userBlocks.blockerId,
      blockedId: schema.userBlocks.blockedId,
    })
    .from(schema.userBlocks)
    .where(
      or(
        eq(schema.userBlocks.blockerId, viewerId),
        eq(schema.userBlocks.blockedId, viewerId),
      ),
    )
  return rows.map((r) =>
    r.blockerId === viewerId ? r.blockedId : r.blockerId,
  )
}

// sql predicate: userCol is not block-related to the viewer, to filter
// content rows (posts, reactions) by their author/actor in-query
export const notBlockedByViewer = (
  userCol: AnyColumn,
  viewerId: string,
) =>
  notExists(
    db
      .select({n: sql`1`})
      .from(schema.userBlocks)
      .where(
        or(
          and(
            eq(schema.userBlocks.blockerId, viewerId),
            eq(schema.userBlocks.blockedId, userCol),
          ),
          and(
            eq(schema.userBlocks.blockedId, viewerId),
            eq(schema.userBlocks.blockerId, userCol),
          ),
        ),
      ),
  )

// block: record the edge, then sever the relationship — drop any friendship
// (accepted or pending) and any pending trail invite between the two.
// accepted shared-trail memberships survive (their content is filtered, not
// torn down). idempotent on the pair index
export const blockUser = async (
  blockerId: string,
  blockedId: string,
): Promise<void> => {
  await db.transaction(async (tx) => {
    await tx
      .insert(schema.userBlocks)
      .values({blockerId, blockedId})
      .onConflictDoNothing({
        target: [
          schema.userBlocks.blockerId,
          schema.userBlocks.blockedId,
        ],
      })
    await tx
      .delete(schema.friendships)
      .where(friendshipPairWhere(blockerId, blockedId))
    await tx
      .delete(schema.trailCollaborators)
      .where(
        and(
          eq(schema.trailCollaborators.status, 'pending'),
          or(
            and(
              eq(schema.trailCollaborators.userId, blockedId),
              eq(schema.trailCollaborators.inviterId, blockerId),
            ),
            and(
              eq(schema.trailCollaborators.userId, blockerId),
              eq(schema.trailCollaborators.inviterId, blockedId),
            ),
          ),
        ),
      )
  })
}

export const unblockUser = async (
  blockerId: string,
  blockedId: string,
): Promise<void> => {
  await db
    .delete(schema.userBlocks)
    .where(
      and(
        eq(schema.userBlocks.blockerId, blockerId),
        eq(schema.userBlocks.blockedId, blockedId),
      ),
    )
}
