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

// Mock the WS plugin (we assert on what extension.service tried to broadcast)
// and the FCM notification service so tests don't try to reach external APIs.
vi.mock('../../src/plugins/websocket.js', () => ({
  sendToUser: vi.fn(() => false),
  sendToSessionParticipant: vi.fn(() => false),
  registerWebSocketPlugin: vi.fn(),
  registerWebSocketRoute: vi.fn(),
  isUserOnlineWs: vi.fn(() => true),
  getSessionConnections: vi.fn(() => ({})),
}))

vi.mock('../../src/services/notification.service.js', () => ({
  sendPushNotification: vi.fn(async () => true),
  registerDeviceToken: vi.fn(async () => {}),
}))

const { sendToSessionParticipant } = await import('../../src/plugins/websocket.js')
const { respondToExtension } = await import('../../src/services/extension.service.js')
const { createPaymentSession, confirmPaymentSession } = await import('../../src/services/payment.service.js')
const {
  WsMessage,
  SessionStatus,
  ExtensionStatus,
} = await import('../../src/constants.js')
const { db, resetDb, resetAppConfig } = await import('../helpers/db.js')
const { createCustomer, createMitra } = await import('../helpers/fixtures.js')

describe('extension.service — EXTENSION_RESPONSE payload', () => {
  let customer
  let mitra

  beforeAll(async () => {
    await resetAppConfig()
  })

  beforeEach(async () => {
    await resetDb()
    customer = await createCustomer({ callName: 'ExtCust' })
    mitra = await createMitra({ callName: 'ExtMitra', isOnline: true })
    sendToSessionParticipant.mockClear()
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  it('accepted extension broadcasts EXTENSION_RESPONSE with the new expires_at', async () => {
    const sql = db()

    // Seed an active chat_sessions row whose timer is about to run out so the
    // extension push has a meaningful baseline to advance.
    const baseExpiresAt = new Date(Date.now() + 30_000) // 30s left
    const [session] = await sql`
      INSERT INTO chat_sessions (customer_id, mitra_id, status, expires_at, duration_minutes)
      VALUES (${customer.id}, ${mitra.id}, ${SessionStatus.ACTIVE}, ${baseExpiresAt}, 12)
      RETURNING id
    `

    // A confirmed extension payment session (is_extension=true).
    const extPay = await createPaymentSession({
      customerId: customer.id,
      durationMinutes: 10,
      amount: 9000,
      isExtension: true,
    })
    await confirmPaymentSession(extPay.id, customer.id)

    // Pending extension row tied to that payment.
    const [extension] = await sql`
      INSERT INTO session_extensions (
        session_id, requested_duration_minutes, requested_price, status, payment_session_id
      )
      VALUES (${session.id}, 10, 9000, ${ExtensionStatus.PENDING}, ${extPay.id})
      RETURNING id
    `

    // Act
    await respondToExtension(extension.id, session.id, mitra.id, true)

    // Find the EXTENSION_RESPONSE call to the customer
    const respCalls = sendToSessionParticipant.mock.calls.filter(
      ([, , payload]) => payload?.type === WsMessage.EXTENSION_RESPONSE,
    )
    expect(respCalls).toHaveLength(1)
    const payload = respCalls[0][2]
    expect(payload.accepted).toBe(true)
    expect(payload.duration_minutes).toBe(10)
    expect(payload.expires_at).toBeTruthy()

    // The new expires_at must be ahead of the seeded baseExpiresAt by ~10 min.
    const newExp = new Date(payload.expires_at).getTime()
    const baseMs = baseExpiresAt.getTime()
    const deltaMin = (newExp - baseMs) / 60_000
    expect(deltaMin).toBeGreaterThan(9.5)
    expect(deltaMin).toBeLessThan(10.5)

    // DB should reflect the same shift.
    const [refreshed] = await sql`SELECT expires_at FROM chat_sessions WHERE id = ${session.id}`
    expect(new Date(refreshed.expires_at).getTime()).toBe(newExp)
  })

  it('rejected extension broadcasts EXTENSION_RESPONSE without expires_at', async () => {
    const sql = db()

    const baseExpiresAt = new Date(Date.now() + 30_000)
    const [session] = await sql`
      INSERT INTO chat_sessions (customer_id, mitra_id, status, expires_at, duration_minutes)
      VALUES (${customer.id}, ${mitra.id}, ${SessionStatus.ACTIVE}, ${baseExpiresAt}, 12)
      RETURNING id
    `
    const extPay = await createPaymentSession({
      customerId: customer.id,
      durationMinutes: 10,
      amount: 9000,
      isExtension: true,
    })
    await confirmPaymentSession(extPay.id, customer.id)
    const [extension] = await sql`
      INSERT INTO session_extensions (
        session_id, requested_duration_minutes, requested_price, status, payment_session_id
      )
      VALUES (${session.id}, 10, 9000, ${ExtensionStatus.PENDING}, ${extPay.id})
      RETURNING id
    `

    await respondToExtension(extension.id, session.id, mitra.id, false)

    const respCalls = sendToSessionParticipant.mock.calls.filter(
      ([, , payload]) => payload?.type === WsMessage.EXTENSION_RESPONSE,
    )
    expect(respCalls).toHaveLength(1)
    const payload = respCalls[0][2]
    expect(payload.accepted).toBe(false)
    // Rejected path does not extend the timer, so no expires_at is sent.
    expect(payload.expires_at).toBeUndefined()
  })
})
