/**
 * Trusted-device query helpers — auth-2fa.
 *
 * Devices can be "remembered" for 30 days after a successful 2FA verification,
 * skipping the 2FA challenge on subsequent logins from the same device.
 * The device trust token plaintext lives in an HttpOnly cookie; only its
 * SHA-256 hash is stored here.
 */
import { and, eq, gt, isNull, sql } from 'drizzle-orm'
import type { UserId } from '@zync/types'
import type { Db } from '../client'
import { userTrustedDevices } from '../schema'
import { auditLog } from './_audit-forward'

export type TrustedDevice = {
  id: string
  userId: string
  tenantId: string
  tokenHash: string
  userAgent: string | null
  ipAddress: string | null
  expiresAt: Date
  revokedAt: Date | null
  createdAt: Date
}

/**
 * Look up an active (non-revoked, non-expired) trusted device by token hash
 * scoped to user + tenant.
 */
export async function findValidTrustedDevice(
  db: Db,
  tokenHash: string,
  userId: UserId,
  tenantId: string,
): Promise<TrustedDevice | null> {
  const [row] = await db
    .select()
    .from(userTrustedDevices)
    .where(
      and(
        eq(userTrustedDevices.tokenHash, tokenHash),
        eq(userTrustedDevices.userId, userId),
        eq(userTrustedDevices.tenantId, tenantId),
        isNull(userTrustedDevices.revokedAt),
        gt(userTrustedDevices.expiresAt, new Date()),
      ),
    )
    .limit(1)
  return row ?? null
}

/** Insert a new trusted device record. */
export async function createTrustedDevice(
  db: Db,
  args: {
    userId: UserId
    tenantId: string
    tokenHash: string
    userAgent?: string
    ipAddress?: string
    expiresAt: Date
  },
  auditArgs?: { actorIp?: string | null; requestId?: string | null },
): Promise<void> {
  await db.transaction(async (tx) => {
    await tx.insert(userTrustedDevices).values({
      userId: args.userId,
      tenantId: args.tenantId,
      tokenHash: args.tokenHash,
      userAgent: args.userAgent ?? null,
      ipAddress: args.ipAddress ?? null,
      expiresAt: args.expiresAt,
    })

    await tx.insert(auditLog).values({
      tenantId: args.tenantId,
      actorId: args.userId,
      actorType: 'user',
      entityType: 'trusted_device',
      entityId: args.userId,
      action: 'trusted_device.created',
      ip: auditArgs?.actorIp ?? null,
      requestId: auditArgs?.requestId ?? null,
    })
  })
}

/**
 * List non-expired, non-revoked trusted devices for a user in a tenant.
 * Used in the security settings UI.
 */
export async function listTrustedDevices(
  db: Db,
  userId: UserId,
  tenantId: string,
): Promise<TrustedDevice[]> {
  return db
    .select()
    .from(userTrustedDevices)
    .where(
      and(
        eq(userTrustedDevices.userId, userId),
        eq(userTrustedDevices.tenantId, tenantId),
        isNull(userTrustedDevices.revokedAt),
        gt(userTrustedDevices.expiresAt, new Date()),
      ),
    )
    .orderBy(userTrustedDevices.createdAt)
}

/** Revoke a specific trusted device (scoped to current user + tenant). */
export async function revokeTrustedDevice(
  db: Db,
  id: string,
  userId: UserId,
  tenantId: string,
  auditArgs?: { actorIp?: string | null; requestId?: string | null },
): Promise<void> {
  await db.transaction(async (tx) => {
    await tx
      .update(userTrustedDevices)
      .set({ revokedAt: new Date() })
      .where(
        and(
          eq(userTrustedDevices.id, id),
          eq(userTrustedDevices.userId, userId),
          eq(userTrustedDevices.tenantId, tenantId),
        ),
      )

    await tx.insert(auditLog).values({
      tenantId,
      actorId: userId,
      actorType: 'user',
      entityType: 'trusted_device',
      entityId: userId,
      action: 'trusted_device.revoked',
      ip: auditArgs?.actorIp ?? null,
      requestId: auditArgs?.requestId ?? null,
    })
  })
}

/** Revoke ALL trusted devices for a user in a specific tenant. */
export async function revokeAllTrustedDevices(
  db: Db,
  userId: UserId,
  tenantId: string,
  auditArgs?: { actorIp?: string | null; requestId?: string | null },
): Promise<void> {
  await db.transaction(async (tx) => {
    await tx
      .update(userTrustedDevices)
      .set({ revokedAt: new Date() })
      .where(
        and(
          eq(userTrustedDevices.userId, userId),
          eq(userTrustedDevices.tenantId, tenantId),
          isNull(userTrustedDevices.revokedAt),
        ),
      )

    await tx.insert(auditLog).values({
      tenantId,
      actorId: userId,
      actorType: 'user',
      entityType: 'trusted_device',
      entityId: userId,
      action: 'trusted_device.revoked_all',
      ip: auditArgs?.actorIp ?? null,
      requestId: auditArgs?.requestId ?? null,
    })
  })
}

/**
 * Revoke ALL trusted devices for an entire tenant.
 * Called when `disable_2fa_remember_device` is enabled on the tenant.
 */
export async function revokeAllTrustedDevicesForTenant(
  db: Db,
  tenantId: string,
  actorId: UserId,
  auditArgs?: { actorIp?: string | null; requestId?: string | null },
): Promise<void> {
  await db.transaction(async (tx) => {
    await tx
      .update(userTrustedDevices)
      .set({ revokedAt: new Date() })
      .where(
        and(
          eq(userTrustedDevices.tenantId, tenantId),
          isNull(userTrustedDevices.revokedAt),
        ),
      )

    await tx.insert(auditLog).values({
      tenantId,
      actorId,
      actorType: 'user',
      entityType: 'trusted_device',
      entityId: tenantId,
      action: 'trusted_device.tenant_revoked_all',
      ip: auditArgs?.actorIp ?? null,
      requestId: auditArgs?.requestId ?? null,
    })
  })
}

/** Count trusted devices for a tenant (for stats). */
export async function countTrustedDevicesForTenant(db: Db, tenantId: string): Promise<number> {
  const [row] = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(userTrustedDevices)
    .where(
      and(
        eq(userTrustedDevices.tenantId, tenantId),
        isNull(userTrustedDevices.revokedAt),
        gt(userTrustedDevices.expiresAt, new Date()),
      ),
    )
  return row?.count ?? 0
}
