import { and, desc, eq, gte, ilike, lt, lte, or } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { decodeCursor, encodeCursor } from './cursor.js'
import { auditLog } from './schema.js'
import type { AuditSchema } from './schema.js'
import type { AuditRecord, ListAuditFilter, ListAuditResult } from './types.js'

function toAuditRecord(row: typeof auditLog.$inferSelect): AuditRecord {
  return {
    id: row.id,
    actorId: row.actorId,
    actorType: row.actorType,
    actorLabel: row.actorLabel,
    action: row.action,
    entityType: row.entityType,
    entityId: row.entityId,
    metadata: row.metadata,
    ip: row.ip,
    tenantId: row.tenantId,
    createdAt: row.createdAt,
  }
}

const DEFAULT_LIMIT = 50
const MAX_LIMIT = 200

/** Clamp untrusted `limit`: non-finite → default; else trunc into [1, MAX_LIMIT]. */
function clampLimit(value: number | undefined): number {
  if (value === undefined || !Number.isFinite(value)) return DEFAULT_LIMIT
  return Math.min(Math.max(Math.trunc(value), 1), MAX_LIMIT)
}

/** Reject an Invalid Date at the boundary rather than emit a cryptic driver RangeError. */
function assertValidDate(value: Date, field: string): void {
  if (Number.isNaN(value.getTime())) {
    throw new RangeError(`listAudit: filter.${field} is not a valid Date`)
  }
}

function buildFilterConditions(filter: ListAuditFilter) {
  const conditions = []

  if (filter.tenant !== undefined) {
    conditions.push(eq(auditLog.tenantId, filter.tenant))
  }
  if (filter.actor !== undefined) {
    conditions.push(eq(auditLog.actorId, filter.actor))
  }
  if (filter.entityType !== undefined) {
    conditions.push(eq(auditLog.entityType, filter.entityType))
  }
  if (filter.entityId !== undefined) {
    conditions.push(eq(auditLog.entityId, filter.entityId))
  }
  if (filter.action !== undefined) {
    conditions.push(eq(auditLog.action, filter.action))
  }
  if (filter.from !== undefined) {
    assertValidDate(filter.from, 'from')
    conditions.push(gte(auditLog.createdAt, filter.from))
  }
  if (filter.to !== undefined) {
    assertValidDate(filter.to, 'to')
    conditions.push(lte(auditLog.createdAt, filter.to))
  }
  if (filter.q !== undefined) {
    const pattern = `%${filter.q}%`
    conditions.push(
      or(
        ilike(auditLog.action, pattern),
        ilike(auditLog.entityType, pattern),
        ilike(auditLog.actorLabel, pattern),
      )!,
    )
  }
  if (filter.cursor !== undefined) {
    const cursor = decodeCursor(filter.cursor)
    const cursorCreatedAt = new Date(cursor.createdAt)
    conditions.push(
      or(
        lt(auditLog.createdAt, cursorCreatedAt),
        and(eq(auditLog.createdAt, cursorCreatedAt), lt(auditLog.id, cursor.id)),
      )!,
    )
  }

  return conditions
}

/**
 * List audit records, newest first, with keyset pagination (cursor = `(created_at_ms, id)`).
 *
 * `tenant` is OPTIONAL (single-tenant hosts store `tenant_id` null). When provided it
 * constrains correctly; when omitted the query spans ALL tenants — scoping is the host's
 * responsibility. On a multi-tenant audit surface, always pass `tenant` (fail-open default).
 */
export async function listAudit<S extends AuditSchema>(
  db: Querier<S>,
  filter: ListAuditFilter = {},
): Promise<ListAuditResult> {
  const limit = clampLimit(filter.limit)
  const conditions = buildFilterConditions(filter)
  const whereClause = conditions.length > 0 ? and(...conditions) : undefined

  const rows = await db
    .select()
    .from(auditLog)
    .where(whereClause)
    .orderBy(desc(auditLog.createdAt), desc(auditLog.id))
    .limit(limit + 1)

  if (rows.length <= limit) {
    return {
      items: rows.map(toAuditRecord),
      nextCursor: null,
    }
  }

  const kept = rows.slice(0, limit)
  const last = kept[kept.length - 1]!

  return {
    items: kept.map(toAuditRecord),
    nextCursor: encodeCursor({ createdAt: last.createdAt, id: last.id }),
  }
}
