import { sql } from 'drizzle-orm'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import type { TransactionalDatabase } from '@platform-modules/db'
import { isFulfillmentValidationError } from '../errors.js'
import { startPg } from '../pg-harness.js'
import type { FulfillmentDbSchema } from '../schema.js'
import type { Address, ShipmentStatus } from '../types.js'
import {
  createMemoryCarrierAdapter,
  createMemoryFulfillmentPorts,
  DEFAULT_MEMORY_ADDRESS,
} from '../testing.js'
import { createShipment } from './shipment.js'
import { handleCarrierWebhook } from './webhook.js'

const ORDER_ID = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd'

const ADDRESS: Address = {
  line1: '9 Webhook Way',
  city: 'Carrier City',
  postalCode: '30303',
  country: 'US',
}

function firstRow(res: unknown): Record<string, unknown> | undefined {
  const rows = (Array.isArray(res) ? res : (res as { rows?: Record<string, unknown>[] }).rows) ?? []
  return rows[0]
}

describe('handleCarrierWebhook (real Postgres)', () => {
  let db: TransactionalDatabase<FulfillmentDbSchema>
  let stop: (() => Promise<void>) | undefined

  beforeAll(async () => {
    const pgResult = await startPg()
    db = pgResult.db
    stop = pgResult.stop
  }, 120_000)

  afterAll(async () => {
    await stop?.()
  }, 30_000)

  async function seedLabeledShipment() {
    const shipment = await db.transaction((tx) =>
      createShipment(tx, {
        orderId: ORDER_ID,
        lineIds: ['line-physical-3'],
        address: ADDRESS,
      }),
    )

    await db.execute(sql`
      UPDATE shipment
      SET status = 'labeled',
          tracking_number = 'TRACK-WEBHOOK-001',
          label_id = 'label-webhook-1',
          carrier_kind = 'memory',
          updated_at = NOW()
      WHERE id = ${shipment.id}::uuid
    `)

    return shipment
  }

  it('same eventId delivered twice applies the status side effect once (M5)', async () => {
    const shipment = await seedLabeledShipment()
    const eventId = 'evt-dedup-001'
    let statusTxCount = 0

    const carrier = createMemoryCarrierAdapter({
      verifyWebhook: async () => ({
        eventId,
        shipmentId: shipment.id,
        trackingNumber: 'TRACK-WEBHOOK-001',
        status: 'in_transit' as const,
      }),
    })

    const ports = createMemoryFulfillmentPorts({
      db: {
        ...db,
        execute: (...args: Parameters<typeof db.execute>) => db.execute(...args),
        transaction: async <T>(fn: (tx: Parameters<Parameters<typeof db.transaction>[0]>[0]) => Promise<T>) => {
          statusTxCount += 1
          return db.transaction(fn)
        },
      } as unknown as TransactionalDatabase<FulfillmentDbSchema>,
    })

    const req = new Request('https://example.test/webhook', { method: 'POST' })

    await handleCarrierWebhook(ports, carrier, req)
    await handleCarrierWebhook(ports, carrier, req)

    expect(statusTxCount).toBe(1)

    const statusRes = await db.execute(sql`
      SELECT status FROM shipment WHERE id = ${shipment.id}::uuid
    `)
    expect(String(firstRow(statusRes)?.status)).toBe('in_transit')

    const claimRes = await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM carrier_webhook_event WHERE event_id = ${eventId}
    `)
    expect(Number(firstRow(claimRes)?.count)).toBe(1)
  })

  it('release-safe: claim row remains when handler throws after the side effect (M6)', async () => {
    const shipment = await seedLabeledShipment()
    const eventId = 'evt-release-safe-001'

    const carrier = createMemoryCarrierAdapter({
      verifyWebhook: async () => ({
        eventId,
        shipmentId: shipment.id,
        trackingNumber: 'TRACK-WEBHOOK-001',
        status: 'delivered' as const,
      }),
    })

    const baseTransaction = db.transaction.bind(db)
    const throwingDb = {
      ...db,
      execute: (...args: Parameters<typeof db.execute>) => db.execute(...args),
      transaction: async <T>(fn: (tx: Parameters<Parameters<typeof db.transaction>[0]>[0]) => Promise<T>) => {
        const result = await baseTransaction(fn)
        throw new Error('post-effect failure')
      },
    } as unknown as TransactionalDatabase<FulfillmentDbSchema>

    const ports = createMemoryFulfillmentPorts({ db: throwingDb })

    const req = new Request('https://example.test/webhook', { method: 'POST' })

    await expect(handleCarrierWebhook(ports, carrier, req)).rejects.toThrow('post-effect failure')

    const claimRes = await db.execute(sql`
      SELECT event_id FROM carrier_webhook_event WHERE event_id = ${eventId}
    `)
    expect(firstRow(claimRes)?.event_id).toBe(eventId)
  })

  it('secaudit-fulfillment-carrier-webhook-status-validated', async () => {
    const shipment = await seedLabeledShipment()
    const eventId = 'evt-invalid-status-001'
    let validDeliveryCount = 0

    const invalidCarrier = createMemoryCarrierAdapter({
      verifyWebhook: async () => ({
        eventId,
        shipmentId: shipment.id,
        trackingNumber: 'TRACK-WEBHOOK-001',
        status: 'not_a_real_status' as ShipmentStatus,
      }),
    })

    const validCarrier = createMemoryCarrierAdapter({
      verifyWebhook: async () => {
        validDeliveryCount += 1
        return {
          eventId,
          shipmentId: shipment.id,
          trackingNumber: 'TRACK-WEBHOOK-001',
          status: 'delivered' as const,
        }
      },
    })

    const ports = createMemoryFulfillmentPorts({ db })
    const req = new Request('https://example.test/webhook', { method: 'POST' })

    await expect(
      handleCarrierWebhook(ports, invalidCarrier, req),
    ).rejects.toSatisfy((error) => isFulfillmentValidationError(error))

    const claimAfterInvalid = await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM carrier_webhook_event WHERE event_id = ${eventId}
    `)
    expect(Number(firstRow(claimAfterInvalid)?.count)).toBe(0)

    await handleCarrierWebhook(ports, validCarrier, req)
    expect(validDeliveryCount).toBe(1)

    const statusRes = await db.execute(sql`
      SELECT status FROM shipment WHERE id = ${shipment.id}::uuid
    `)
    expect(String(firstRow(statusRes)?.status)).toBe('delivered')

    const claimAfterValid = await db.execute(sql`
      SELECT COUNT(*)::int AS count FROM carrier_webhook_event WHERE event_id = ${eventId}
    `)
    expect(Number(firstRow(claimAfterValid)?.count)).toBe(1)
  })
})
