import { and, desc, eq, lt, or } from 'drizzle-orm'
import type { Querier, Schema } from '@platform-modules/db'
import { clampLimit, decodeCursor, encodeCursor } from '../cursor.js'
import { deriveStored } from '../derive.js'
import type { ListFilter, Page, RecordStore, Stored } from '../types.js'
import { recordStoreTable } from './table.js'

export { recordStoreTable, recordStoreSchema } from './table.js'
export type { RecordStoreSchema } from './table.js'

export type QuerierRecordStoreOptions = {
  now?: () => string
  makeId?: () => string
}

type RecordStoreRow = typeof recordStoreTable.$inferSelect

function rowToStored<T>(row: RecordStoreRow): Stored<T> {
  return {
    id: row.id,
    createdAt: new Date(row.createdAtMs).toISOString(),
    record: row.record as T,
  }
}

function snapshotStored<T>(stored: Stored<T>): Stored<T> {
  return {
    id: stored.id,
    createdAt: stored.createdAt,
    record: JSON.parse(JSON.stringify(stored.record)) as T,
  }
}

export function makeQuerierRecordStore<T, S extends Schema>(
  db: Querier<S>,
  table: typeof recordStoreTable = recordStoreTable,
  opts: QuerierRecordStoreOptions = {},
): RecordStore<T> {
  const now = opts.now ?? (() => new Date().toISOString())
  const makeId = opts.makeId ?? (() => crypto.randomUUID())

  return {
    async append(record: T): Promise<Stored<T>> {
      const derived = deriveStored(record, now, makeId)

      const [row] = await db
        .insert(table)
        .values({
          id: derived.id,
          createdAtMs: derived.createdAtMs,
          record: derived.record,
        })
        .returning()

      if (!row) {
        throw new Error('makeQuerierRecordStore.append: insert returned no row')
      }

      return snapshotStored({
        id: row.id,
        createdAt: derived.createdAt,
        record: row.record as T,
      })
    },

    async list(filter: ListFilter = {}): Promise<Page<Stored<T>>> {
      const limit = clampLimit(filter.limit)
      const conditions = []

      if (filter.cursor !== undefined) {
        const cursor = decodeCursor(filter.cursor)
        conditions.push(
          or(
            lt(table.createdAtMs, cursor.createdAtMs),
            and(eq(table.createdAtMs, cursor.createdAtMs), lt(table.seq, cursor.seq)),
          )!,
        )
      }

      const whereClause = conditions.length > 0 ? and(...conditions) : undefined

      const rows = await db
        .select()
        .from(table)
        .where(whereClause)
        .orderBy(desc(table.createdAtMs), desc(table.seq))
        .limit(limit + 1)

      if (rows.length <= limit) {
        return {
          items: rows.map((row) => snapshotStored(rowToStored<T>(row))),
          nextCursor: null,
        }
      }

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

      return {
        items: kept.map((row) => snapshotStored(rowToStored<T>(row))),
        nextCursor: encodeCursor({ createdAtMs: last.createdAtMs, seq: last.seq }),
      }
    },
  }
}
