import { and, desc, eq, lt } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { validateSubmission, toRecord } from './validate.js'
import { checkAntiSpam } from './antispam.js'
import { formSubmissions } from './schema.js'
import type { FormsSchema } from './schema.js'
import type { FieldError, FormDef, Result, StoredSubmission } from './types.js'

// Re-export the table + schema + DDL helper so an adopter (and the Gate-3 smoke) gets them from
// the `./store` subpath — the import-light `.` barrel must NOT carry drizzle schema. Migrations
// and the host deploy script (formsTableSql) need these.
export { formSubmissions, formsSchema, formsTableSql } from './schema.js'
export type { FormsSchema } from './schema.js'

export interface SubmissionStore {
  record(s: Omit<StoredSubmission, 'id' | 'createdAt'>): Promise<StoredSubmission>
  get(formId: string, id: string): Promise<StoredSubmission | null>
  list(
    formId: string,
    opts?: { limit?: number; cursor?: string; includeSpam?: boolean },
  ): Promise<{ items: StoredSubmission[]; nextCursor?: string }>
  delete(formId: string, id: string): Promise<void>
}

/**
 * Cap on each host-supplied `meta` string (ip/userAgent/ref). These are persisted, attacker-
 * influenced header values (User-Agent, Referer) — the module's "bounds every STORED value"
 * doctrine applies to them too, not just declared fields. 2k is generous for any real header.
 */
const META_MAX = 2_000
const capMeta = (v: string | undefined): string | undefined =>
  v === undefined ? undefined : v.length > META_MAX ? v.slice(0, META_MAX) : v

function rowToSubmission(row: typeof formSubmissions.$inferSelect): StoredSubmission {
  return {
    id: row.id,
    formId: row.formId,
    data: row.data,
    createdAt: row.createdAt.toISOString(),
    meta: row.meta ?? undefined,
    spam: row.spam,
  }
}

/**
 * Reference adapter over the `db` type-contract. All reads/writes are SCOPED by formId (IDOR floor):
 * a submission read MUST be authorized by the host before calling list/get; the store never
 * exposes a cross-form global read. Cursor = the last row's createdAt ISO (keyset by created_at desc).
 */
export function createDbSubmissionStore(db: Querier<FormsSchema>): SubmissionStore {
  return {
    async record(s) {
      const rows = await db
        .insert(formSubmissions)
        .values({ formId: s.formId, data: s.data, meta: s.meta, spam: s.spam ?? false })
        .returning()
      return rowToSubmission(rows[0]!)
    },
    async get(formId, id) {
      const rows = await db
        .select()
        .from(formSubmissions)
        .where(and(eq(formSubmissions.id, id), eq(formSubmissions.formId, formId)))
        .limit(1)
      return rows[0] ? rowToSubmission(rows[0]) : null
    },
    async list(formId, opts) {
      const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 200)
      const conds = [eq(formSubmissions.formId, formId)]
      if (!opts?.includeSpam) conds.push(eq(formSubmissions.spam, false))
      if (opts?.cursor) conds.push(lt(formSubmissions.createdAt, new Date(opts.cursor)))
      const rows = await db
        .select()
        .from(formSubmissions)
        .where(and(...conds))
        .orderBy(desc(formSubmissions.createdAt))
        .limit(limit + 1)
      const items = rows.slice(0, limit).map(rowToSubmission)
      const nextCursor = rows.length > limit ? items[items.length - 1]!.createdAt : undefined
      return { items, nextCursor }
    },
    async delete(formId, id) {
      await db
        .delete(formSubmissions)
        .where(and(eq(formSubmissions.id, id), eq(formSubmissions.formId, formId)))
    },
  }
}

/**
 * The one call a host endpoint wires: validate → anti-spam (sets the spam flag) → store.record.
 * Validation failure => Result error and NOTHING is stored. A spam verdict is stored with
 * spam=true (quarantine for review) — FLAG not drop (data-loss floor).
 */
export async function recordSubmission(args: {
  form: FormDef
  raw: Record<string, unknown> | FormData
  store: SubmissionStore
  antispam: {
    renderToken?: string
    secret?: string
    now: number
    challengeToken?: string
    ip?: string
    userAgent?: string
    ref?: string
  }
}): Promise<Result<StoredSubmission, FieldError[]>> {
  const valid = validateSubmission(args.form, args.raw)
  if (!valid.ok) return valid // never stored on validation failure

  const verdict = await checkAntiSpam(args.form, toRecord(args.raw), {
    renderToken: args.antispam.renderToken,
    secret: args.antispam.secret,
    now: args.antispam.now,
    challengeToken: args.antispam.challengeToken,
    ip: args.antispam.ip,
  })

  const stored = await args.store.record({
    formId: args.form.id,
    data: valid.value,
    meta: {
      ip: capMeta(args.antispam.ip),
      userAgent: capMeta(args.antispam.userAgent),
      ref: capMeta(args.antispam.ref),
    },
    spam: !verdict.ok, // FLAG not drop — quarantine for review
  })
  return { ok: true, value: stored }
}
