import {and, eq, exists, or} from 'drizzle-orm'
import {HTTPException} from 'hono/http-exception'

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

// read rules that cut across domains, in one place: trail membership as a
// sql predicate, and the crumb-read rule composed from friendship + shared
// trails. domain services compose these; none re-encodes them

// correlated subquery: user is an accepted collaborator on the outer trails row
export const isAcceptedCollaborator = (userId: string) =>
  exists(
    db
      .select({id: schema.trailCollaborators.id})
      .from(schema.trailCollaborators)
      .where(
        and(
          eq(schema.trailCollaborators.trailId, schema.trails.id),
          eq(schema.trailCollaborators.userId, userId),
          eq(schema.trailCollaborators.status, 'accepted'),
        ),
      ),
  )

export const sharesTrailWithCrumb = async (
  viewerId: string,
  crumbId: string,
): Promise<boolean> => {
  const [row] = await db
    .select({trailId: schema.trailCrumbs.trailId})
    .from(schema.trailCrumbs)
    .innerJoin(
      schema.trails,
      eq(schema.trails.id, schema.trailCrumbs.trailId),
    )
    .where(
      and(
        eq(schema.trailCrumbs.crumbId, crumbId),
        or(
          eq(schema.trails.authorId, viewerId),
          isAcceptedCollaborator(viewerId),
        ),
      ),
    )
    .limit(1)
  return Boolean(row)
}

// the app's central crumb-read rule: self, a friend of the author, or one
// the viewer already shares a trail with. the single source for both the
// crumb detail read and filing a crumb into a trail
export const isCrumbViewable = async (
  viewerId: string,
  crumb: {id: string; authorId: string},
): Promise<boolean> =>
  crumb.authorId === viewerId ||
  (await areFriends(viewerId, crumb.authorId)) ||
  (await sharesTrailWithCrumb(viewerId, crumb.id))

// load a crumb the viewer may file into a trail, or reject. 404 for a
// missing crumb, 403 (not_friends) for one they cannot see — mirroring the
// 403/404 split of GET /crumbs/:id
export const requireViewableCrumb = async (
  crumbId: string,
  viewerId: string,
): Promise<{id: string; name: string; authorId: string}> => {
  const [crumb] = await db
    .select({
      id: schema.crumbs.id,
      name: schema.crumbs.name,
      authorId: schema.crumbs.authorId,
    })
    .from(schema.crumbs)
    .where(eq(schema.crumbs.id, crumbId))
    .limit(1)
  if (!crumb) throw new HTTPException(404, {message: 'not_found'})
  if (!(await isCrumbViewable(viewerId, crumb)))
    throw new HTTPException(403, {message: 'not_friends'})
  return crumb
}
