import Fastify from 'fastify'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NOTIFICATION_EVENTS } from '../../../../shared/constants/notification-events.js'

const { mockPrisma } = vi.hoisted(() => ({
  mockPrisma: {
    payment: { findFirst: vi.fn(), update: vi.fn() },
    order: { findFirst: vi.fn(), updateMany: vi.fn() },
  },
}))

vi.mock('../../lib/prisma.js', () => ({
  prisma: mockPrisma,
}))

vi.mock('../../lib/yookassa.js', () => ({
  validateWebhook: vi.fn(),
}))

import { validateWebhook } from '../../lib/yookassa.js'
import { registerYookassaWebhookRoute } from '../webhook-yookassa.js'

function buildApp(eventBusMock) {
  const app = Fastify({ logger: false })
  app.decorate('eventBus', eventBusMock || { emit: () => {} })
  return app
}

describe('POST /api/webhooks/yookassa', () => {
  let app
  let eventBus

  beforeEach(async () => {
    eventBus = { emit: vi.fn() }
    validateWebhook.mockImplementation((_ip, body) => {
      if (!body || typeof body !== 'object') throw new Error('Invalid webhook body')
      if (body.type !== 'notification') throw new Error('Expected notification type in webhook body')
      if (!body.event || !body.object) throw new Error('Missing event or object in webhook body')
      return { event: body.event, paymentObject: body.object }
    })
    app = buildApp(eventBus)
    await registerYookassaWebhookRoute(app)
    await app.ready()
  })

  afterEach(async () => {
    await app.close()
    vi.clearAllMocks()
  })

  it('returns 400 for invalid body', async () => {
    const res = await app.inject({
      method: 'POST',
      url: '/api/webhooks/yookassa',
      payload: { not: 'valid' },
    })
    expect(res.statusCode).toBe(400)
  })

  it('returns 404 when payment not found', async () => {
    mockPrisma.payment.findFirst.mockResolvedValue(null)
    const res = await app.inject({
      method: 'POST',
      url: '/api/webhooks/yookassa',
      payload: {
        type: 'notification',
        event: 'payment.succeeded',
        object: { id: 'unknown-id', status: 'succeeded', paid: true },
      },
    })
    expect(res.statusCode).toBe(404)
  })

  it('updates payment and order on payment.succeeded', async () => {
    mockPrisma.payment.findFirst.mockResolvedValue({
      id: 'payment-1',
      yookassaPaymentId: 'yk-id',
      status: 'pending',
      orderId: 'order-1',
    })
    mockPrisma.payment.update.mockResolvedValue({})
    mockPrisma.order.findFirst.mockResolvedValue({
      id: 'order-1',
      status: 'PENDING_PAYMENT',
      userId: 'user-1',
    })
    mockPrisma.order.updateMany.mockResolvedValue({ count: 1 })

    const res = await app.inject({
      method: 'POST',
      url: '/api/webhooks/yookassa',
      payload: {
        type: 'notification',
        event: 'payment.succeeded',
        object: { id: 'yk-id', status: 'succeeded', paid: true },
      },
    })
    expect(res.statusCode).toBe(200)

    const updateData = mockPrisma.payment.update.mock.calls[0][0].data
    expect(updateData.status).toBe('succeeded')

    const orderUpdateData = mockPrisma.order.updateMany.mock.calls[0][0].data
    expect(orderUpdateData.status).toBe('PAID')
    expect(eventBus.emit).toHaveBeenCalledWith(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
      orderId: 'order-1',
      userId: 'user-1',
      paymentStatus: 'paid',
    })
  })

  it('updates payment on payment.canceled without changing order', async () => {
    mockPrisma.payment.findFirst.mockResolvedValue({
      id: 'payment-1',
      yookassaPaymentId: 'yk-id',
      status: 'pending',
      orderId: 'order-1',
    })
    mockPrisma.payment.update.mockResolvedValue({})

    const res = await app.inject({
      method: 'POST',
      url: '/api/webhooks/yookassa',
      payload: {
        type: 'notification',
        event: 'payment.canceled',
        object: { id: 'yk-id', status: 'canceled', paid: false },
      },
    })
    expect(res.statusCode).toBe(200)
    expect(mockPrisma.order.findFirst).not.toHaveBeenCalled()
  })
})
