import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'

vi.mock('../../../src/plugins/websocket.js', () => ({
  sendToUser: vi.fn(() => false),
  sendToSessionParticipant: vi.fn(() => false),
  registerWebSocketPlugin: vi.fn(async () => {}),
  registerWebSocketRoute: vi.fn(),
  isUserOnlineWs: vi.fn(() => false),
  getSessionConnections: vi.fn(() => ({})),
}))
vi.mock('../../../src/services/notification.service.js', () => ({
  sendPushNotification: vi.fn(async () => true),
  registerDeviceToken: vi.fn(async () => {}),
}))

const { buildInternal } = await import('../../helpers/server.js')
const { resetAppConfig, db } = await import('../../helpers/db.js')
const { createCcUser } = await import('../../helpers/fixtures.js')
const { ccJwt, authHeader } = await import('../../helpers/jwt.js')

/**
 * Stage 3 tests for the relational first-session-discount endpoints.
 *
 * The migration seeded the single 'first_session' promotion row; we mutate values
 * inside the test and restore in afterAll so other test files inherit clean state.
 */
describe('/internal/config/first-session-discount', () => {
  let app
  let ccUser
  let token
  let initialSnapshot

  beforeAll(async () => {
    await resetAppConfig()
    app = await buildInternal()
    ccUser = await createCcUser({ displayName: 'DiscountOperator' })
    token = ccJwt(ccUser.id)

    // Snapshot the pre-test row so we can restore it after the suite.
    const sql = db()
    const [row] = await sql`
      SELECT id, enabled, actual_price_idr, gimmick_price_idr, duration_minutes, modes
      FROM pricing_promotions WHERE eligibility = 'first_session'
    `
    initialSnapshot = row
  })

  afterAll(async () => {
    if (initialSnapshot) {
      const sql = db()
      await sql`
        UPDATE pricing_promotions SET
          enabled           = ${initialSnapshot.enabled},
          actual_price_idr  = ${initialSnapshot.actual_price_idr},
          gimmick_price_idr = ${initialSnapshot.gimmick_price_idr},
          duration_minutes  = ${initialSnapshot.duration_minutes},
          modes             = ${initialSnapshot.modes},
          updated_at        = NOW()
        WHERE id = ${initialSnapshot.id}
      `
      // Drop any history rows this test file authored so the table doesn't bloat.
      await sql`
        DELETE FROM pricing_promotions_history
        WHERE promotion_id = ${initialSnapshot.id} AND changed_by = ${ccUser.id}
      `
    }
    await app?.close()
  })

  it('GET returns the current promotion row including updated_at', async () => {
    const res = await app.inject({
      method: 'GET',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
    })
    expect(res.statusCode).toBe(200)
    const body = res.json()
    expect(body.success).toBe(true)
    expect(body.data.eligibility).toBe('first_session')
    expect(typeof body.data.enabled).toBe('boolean')
    expect(typeof body.data.actual_price_idr).toBe('number')
    expect(typeof body.data.duration_minutes).toBe('number')
    expect(Array.isArray(body.data.modes)).toBe(true)
    expect(body.data.updated_at).toBeDefined()
  })

  it('PATCH with correct updated_at updates the row and writes an update history row', async () => {
    const getRes = await app.inject({
      method: 'GET',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
    })
    const current = getRes.json().data

    const patchRes = await app.inject({
      method: 'PATCH',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
      payload: {
        updated_at: current.updated_at,
        actual_price_idr: 2500,
        duration_minutes: 15,
        modes: ['chat', 'call'],
      },
    })
    expect(patchRes.statusCode).toBe(200)
    const updated = patchRes.json().data
    expect(updated.actual_price_idr).toBe(2500)
    expect(updated.duration_minutes).toBe(15)
    expect(updated.modes).toEqual(['chat', 'call'])
    // updated_at advanced.
    expect(new Date(updated.updated_at).getTime()).toBeGreaterThanOrEqual(new Date(current.updated_at).getTime())

    // History row.
    const sql = db()
    const history = await sql`
      SELECT change_kind, changed_by, actual_price_idr, duration_minutes, modes
      FROM pricing_promotions_history
      WHERE promotion_id = ${current.id} AND changed_by = ${ccUser.id}
      ORDER BY changed_at DESC LIMIT 1
    `
    expect(history).toHaveLength(1)
    expect(history[0].change_kind).toBe('update')
    expect(history[0].actual_price_idr).toBe(2500)
    expect(history[0].duration_minutes).toBe(15)
    expect(history[0].modes).toEqual(['chat', 'call'])
  })

  it('PATCH with stale updated_at returns 409 STALE_WRITE', async () => {
    const getRes = await app.inject({
      method: 'GET',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
    })
    const current = getRes.json().data

    const stale = new Date(new Date(current.updated_at).getTime() - 60_000).toISOString()
    const patchRes = await app.inject({
      method: 'PATCH',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
      payload: { updated_at: stale, actual_price_idr: 3000 },
    })
    expect(patchRes.statusCode).toBe(409)
    const err = patchRes.json().error
    expect(err.code).toBe('STALE_WRITE')
    expect(err.server_updated_at).toBeDefined()
  })

  it('PATCH without updated_at returns 422', async () => {
    const res = await app.inject({
      method: 'PATCH',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
      payload: { actual_price_idr: 3000 },
    })
    expect(res.statusCode).toBe(422)
    expect(res.json().error.field).toBe('updated_at')
  })

  it('PATCH with invalid modes returns 422', async () => {
    const getRes = await app.inject({
      method: 'GET',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
    })
    const current = getRes.json().data

    const res = await app.inject({
      method: 'PATCH',
      url: '/internal/config/first-session-discount',
      headers: authHeader(token),
      payload: { updated_at: current.updated_at, modes: ['video'] },
    })
    expect(res.statusCode).toBe(422)
    expect(res.json().error.field).toBe('modes')
  })
})
