import { sql, eq } from 'drizzle-orm'
import { describe, expect, it } from 'vitest'
import { createPgliteClient } from '@platform-modules/db/pglite'
import { hashToken } from '@platform-modules/util/tokens'
import { rotateRefreshToken, bumpSessionVersion, GRACE_WINDOW_MS } from './refresh.js'
import { customAuthSchema } from './schema.js'
import { signSession } from './session.js'
import { CREATE_AUTH_USERS, CREATE_USER_SESSIONS } from './test-ddl.js'

const JWT_SECRET = 'refresh-test-secret'

async function setupDb() {
  const db = createPgliteClient({ schema: customAuthSchema })
  await db.execute(CREATE_AUTH_USERS)
  await db.execute(CREATE_USER_SESSIONS)
  return db
}

async function seedSession(db: Awaited<ReturnType<typeof setupDb>>, refreshToken: string) {
  const userId = 'user-1'
  const sessionId = 'session-1'
  await db.execute(sql`
    INSERT INTO auth_users (id, email, password_hash, session_version, roles, created_at)
    VALUES (${userId}, 'u@example.com', 'x', 0, '["user"]', NOW())
  `)
  const refreshHash = await hashToken(refreshToken)
  await db.execute(sql`
    INSERT INTO user_sessions (id, user_id, refresh_token_hash, expires_at, status)
    VALUES (${sessionId}, ${userId}, ${refreshHash}, NOW() + INTERVAL '30 days', 'active')
  `)
  return { userId, sessionId, refreshHash }
}

describe('rotateRefreshToken (P2)', () => {
  it('rotates refresh token and invalidates the old hash', async () => {
    const db = await setupDb()
    const oldRt = 'opaque-refresh-token-aaaaaaaa'
    await seedSession(db, oldRt)

    const first = await rotateRefreshToken(db, customAuthSchema, oldRt, [JWT_SECRET])
    expect(first.kind).toBe('rotated')
    if (first.kind !== 'rotated') return

    const second = await rotateRefreshToken(db, customAuthSchema, oldRt, [JWT_SECRET])
    expect(second.kind).not.toBe('rotated')
  })

  it('allows only one winner under concurrent rotation', async () => {
    const db = await setupDb()
    const rt = 'race-refresh-token-bbbbbbbb'
    await seedSession(db, rt)

    const [a, b] = await Promise.all([
      rotateRefreshToken(db, customAuthSchema, rt, [JWT_SECRET]),
      rotateRefreshToken(db, customAuthSchema, rt, [JWT_SECRET]),
    ])

    const kinds = [a.kind, b.kind].sort()
    expect(kinds).toEqual(['race_lost', 'rotated'])
  })

  it('family-revokes on consumed RT replay past grace', async () => {
    const db = await setupDb()
    const rt = 'reuse-refresh-token-cccccccc'
    const { userId } = await seedSession(db, rt)

    const rotated = await rotateRefreshToken(db, customAuthSchema, rt, [JWT_SECRET])
    expect(rotated.kind).toBe('rotated')
    if (rotated.kind !== 'rotated') return

    await db.execute(sql`
      UPDATE user_sessions
      SET last_refreshed_at = NOW() - (${GRACE_WINDOW_MS} + 1000) * INTERVAL '1 millisecond'
      WHERE id = 'session-1'
    `)

    const replay = await rotateRefreshToken(db, customAuthSchema, rt, [JWT_SECRET])
    expect(replay.kind).toBe('reuse_detected')
    if (replay.kind !== 'reuse_detected') return
    expect(replay.newSessionVersion).toBe(1)

    const at = await signSession({ sub: userId, sid: 'session-1', sv: 0 }, JWT_SECRET, 900)
    const { verifySession } = await import('./session.js')
    await expect(
      verifySession(at, JWT_SECRET, { minSessionVersion: replay.newSessionVersion }),
    ).resolves.toBeNull()
  })

  it('does not family-revoke an ordinary expired current RT', async () => {
    const db = await setupDb()
    const rt = 'expired-never-rotated-dddddddd'
    await seedSession(db, rt)
    await db.execute(sql`
      UPDATE user_sessions SET expires_at = NOW() - INTERVAL '1 day' WHERE id = 'session-1'
    `)

    const result = await rotateRefreshToken(db, customAuthSchema, rt, [JWT_SECRET])
    expect(result.kind).toBe('invalid')

    const [row] = await db
      .select({ sessionVersion: customAuthSchema.authUsers.sessionVersion })
      .from(customAuthSchema.authUsers)
      .where(eq(customAuthSchema.authUsers.id, 'user-1'))
    expect(row?.sessionVersion).toBe(0)
  })
})

describe('bumpSessionVersion', () => {
  it('increments sessionVersion on the identity row', async () => {
    const db = await setupDb()
    await db.execute(sql`
      INSERT INTO auth_users (id, email, session_version, roles, created_at)
      VALUES ('u-bump', 'b@example.com', 0, '["user"]', NOW())
    `)
    const sv = await bumpSessionVersion(db, customAuthSchema.authUsers, 'u-bump')
    expect(sv).toBe(1)
  })
})
