import { and, desc, eq, lt, or, type SQL } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { ReviewValidationError } from './errors.js'
import { REVIEW_ID_RE } from './ids.js'
import { rowToReview } from './row-to-review.js'
import { review, type ReviewsSchema } from './schema.js'
import {
  REVIEW_LIST_DEFAULT_LIMIT,
  REVIEW_LIST_MAX_LIMIT,
  type Page,
  type Review,
} from './types.js'

const CURSOR_SEP = '|'

export const REVIEW_CURSOR_MAX = 256

export interface ListReviewsOptions {
  audience: 'public' | 'admin'
  vendorId?: string
  cursor?: string
  limit?: number
}

function resolveLimit(limit: number | undefined): number {
  const raw = limit ?? REVIEW_LIST_DEFAULT_LIMIT
  if (!Number.isFinite(raw) || raw < 1) {
    return REVIEW_LIST_DEFAULT_LIMIT
  }
  return Math.min(Math.floor(raw), REVIEW_LIST_MAX_LIMIT)
}

function encodeCursor(createdAt: Date, id: string): string {
  return `${createdAt.toISOString()}${CURSOR_SEP}${id}`
}

function decodeCursor(cursor: string): { createdAt: Date; id: string } {
  if (cursor.length > REVIEW_CURSOR_MAX) {
    throw new ReviewValidationError('cursor')
  }
  const sep = cursor.lastIndexOf(CURSOR_SEP)
  if (sep <= 0) {
    throw new ReviewValidationError('cursor')
  }
  const createdAtRaw = cursor.slice(0, sep)
  const id = cursor.slice(sep + 1)
  const createdAt = new Date(createdAtRaw)
  if (Number.isNaN(createdAt.getTime())) {
    throw new ReviewValidationError('cursor')
  }
  if (!REVIEW_ID_RE.test(id)) {
    throw new ReviewValidationError('cursor')
  }
  return { createdAt, id }
}

function buildCursorPredicate(cursor: string): SQL {
  const { createdAt, id } = decodeCursor(cursor)
  return or(
    lt(review.createdAt, createdAt),
    and(eq(review.createdAt, createdAt), lt(review.id, id)),
  )!
}

export async function listReviews(
  q: Querier<ReviewsSchema>,
  productId: string,
  opts: ListReviewsOptions,
): Promise<Page<Review>> {
  const limit = resolveLimit(opts.limit)
  const predicates: SQL[] = [eq(review.productId, productId)]

  if (opts.audience === 'public') {
    predicates.push(eq(review.status, 'approved'))
  }

  if (opts.vendorId !== undefined) {
    predicates.push(eq(review.vendorId, opts.vendorId))
  }

  if (opts.cursor !== undefined) {
    predicates.push(buildCursorPredicate(opts.cursor))
  }

  const whereClause = and(...predicates)

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

  const hasMore = rows.length > limit
  const pageRows = hasMore ? rows.slice(0, limit) : rows
  const items = pageRows.map(rowToReview)

  const last = pageRows[pageRows.length - 1]
  const nextCursor =
    hasMore && last !== undefined ? encodeCursor(last.createdAt, last.id) : null

  return { items, nextCursor }
}

export interface ListReviewsByVendorOptions {
  audience: 'public' | 'admin'
  productId?: string
  cursor?: string
  limit?: number
}

export async function listReviewsByVendor(
  q: Querier<ReviewsSchema>,
  vendorId: string,
  opts: ListReviewsByVendorOptions,
): Promise<Page<Review>> {
  const limit = resolveLimit(opts.limit)
  const predicates: SQL[] = [eq(review.vendorId, vendorId)]

  if (opts.productId !== undefined) {
    predicates.push(eq(review.productId, opts.productId))
  }

  if (opts.audience === 'public') {
    predicates.push(eq(review.status, 'approved'))
  }

  if (opts.cursor !== undefined) {
    predicates.push(buildCursorPredicate(opts.cursor))
  }

  const whereClause = and(...predicates)

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

  const hasMore = rows.length > limit
  const pageRows = hasMore ? rows.slice(0, limit) : rows
  const items = pageRows.map(rowToReview)

  const last = pageRows[pageRows.length - 1]
  const nextCursor =
    hasMore && last !== undefined ? encodeCursor(last.createdAt, last.id) : null

  return { items, nextCursor }
}
