import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { RazorpayWebhookPayload } from '../../src/providers/payments/razorpay.provider';
import type { RazorpayWebhookEventRow } from '../../src/services/payments/razorpay/webhook.service';
import type { RazorpayEnvironment } from '../../src/types/payments';

const { mockConfigService, mockProvider, mockPool } = vi.hoisted(() => ({
  mockConfigService: {
    getRazorpayWebhookSecret: vi.fn(),
    createRazorpayProvider: vi.fn(),
  },
  mockProvider: {
    verifyWebhookSignature: vi.fn(),
  },
  mockPool: {
    query: vi.fn(),
  },
}));

vi.mock('../../src/infra/database/database.manager', () => ({
  DatabaseManager: {
    getInstance: () => ({
      getPool: () => mockPool,
    }),
  },
}));

vi.mock('../../src/services/payments/razorpay/config.service', () => ({
  RazorpayConfigService: {
    getInstance: () => mockConfigService,
  },
}));

import { RazorpayWebhookService } from '../../src/services/payments/razorpay/webhook.service';

interface RazorpayWebhookServiceInternals {
  applyRazorpayWebhookEvent(
    environment: RazorpayEnvironment,
    payload: RazorpayWebhookPayload
  ): Promise<boolean>;
}

function getServiceInternals(service: RazorpayWebhookService): RazorpayWebhookServiceInternals {
  return service as unknown as RazorpayWebhookServiceInternals;
}

function makeWebhookRow(overrides: Partial<RazorpayWebhookEventRow> = {}): RazorpayWebhookEventRow {
  return {
    id: 'evt_row_123',
    environment: 'test',
    eventId: 'evt_123',
    eventType: 'payment.captured',
    processingStatus: 'pending',
    attemptCount: 1,
    lastError: null,
    receivedAt: '2026-06-05T00:00:00.000Z',
    processedAt: null,
    ...overrides,
  };
}

function makeRawWebhookBody(event: string): Buffer {
  return Buffer.from(
    JSON.stringify({
      entity: 'event',
      account_id: 'acc_123',
      event,
      contains: ['payment'],
      payload: {
        payment: {
          entity: {
            id: 'pay_123',
          },
        },
      },
      created_at: 1780617600,
    })
  );
}

function makeWebhookPayload(event: string): RazorpayWebhookPayload {
  return JSON.parse(makeRawWebhookBody(event).toString('utf8')) as RazorpayWebhookPayload;
}

describe('RazorpayWebhookService', () => {
  beforeEach(() => {
    vi.restoreAllMocks();
    vi.clearAllMocks();
    mockConfigService.getRazorpayWebhookSecret.mockResolvedValue('whsec_123');
    mockConfigService.createRazorpayProvider.mockResolvedValue(mockProvider);
    mockProvider.verifyWebhookSignature.mockReturnValue(true);
    mockPool.query.mockResolvedValue({ rows: [], rowCount: 0 });
  });

  it('processes handled Razorpay events before acknowledging them', async () => {
    const service = RazorpayWebhookService.getInstance();
    const recordSpy = vi
      .spyOn(service, 'recordWebhookEventStart')
      .mockResolvedValue({ shouldProcess: true, row: makeWebhookRow() });
    const markSpy = vi.spyOn(service, 'markWebhookEvent').mockResolvedValue(makeWebhookRow());
    const applySpy = vi
      .spyOn(getServiceInternals(service), 'applyRazorpayWebhookEvent')
      .mockResolvedValue(true);

    const result = await service.handleRazorpayWebhook(
      'test',
      makeRawWebhookBody('payment.captured'),
      'signature',
      'evt_header_123'
    );

    expect(result).toEqual({ received: true, handled: true });
    expect(mockProvider.verifyWebhookSignature).toHaveBeenCalledWith(
      expect.any(Buffer),
      'signature',
      'whsec_123'
    );
    expect(recordSpy).toHaveBeenCalledWith(
      'test',
      'evt_header_123',
      'payment.captured',
      expect.objectContaining({ event: 'payment.captured' })
    );
    expect(applySpy).toHaveBeenCalledWith(
      'test',
      expect.objectContaining({ event: 'payment.captured' })
    );
    expect(markSpy).toHaveBeenCalledWith('test', 'evt_header_123', 'processed', null);
  });

  it('marks unhandled Razorpay events ignored without syncing', async () => {
    const service = RazorpayWebhookService.getInstance();
    vi.spyOn(service, 'recordWebhookEventStart').mockResolvedValue({
      shouldProcess: true,
      row: makeWebhookRow({ eventType: 'customer.created' }),
    });
    const markSpy = vi.spyOn(service, 'markWebhookEvent').mockResolvedValue(makeWebhookRow());
    const applySpy = vi
      .spyOn(getServiceInternals(service), 'applyRazorpayWebhookEvent')
      .mockResolvedValue(true);

    const result = await service.handleRazorpayWebhook(
      'test',
      makeRawWebhookBody('customer.created'),
      'signature',
      'evt_header_456'
    );

    expect(result).toEqual({ received: true, handled: false });
    expect(markSpy).toHaveBeenCalledWith('test', 'evt_header_456', 'ignored', null);
    expect(applySpy).not.toHaveBeenCalled();
  });

  it('marks handled Razorpay events failed and rethrows processing failures', async () => {
    const service = RazorpayWebhookService.getInstance();
    vi.spyOn(service, 'recordWebhookEventStart').mockResolvedValue({
      shouldProcess: true,
      row: makeWebhookRow(),
    });
    const markSpy = vi.spyOn(service, 'markWebhookEvent').mockResolvedValue(makeWebhookRow());
    vi.spyOn(getServiceInternals(service), 'applyRazorpayWebhookEvent').mockRejectedValue(
      new Error('handler failed')
    );

    await expect(
      service.handleRazorpayWebhook(
        'test',
        makeRawWebhookBody('payment.captured'),
        'signature',
        'evt_header_789'
      )
    ).rejects.toThrow('handler failed');

    expect(markSpy).toHaveBeenCalledWith('test', 'evt_header_789', 'failed', 'handler failed');
  });

  it('records new Razorpay webhook events for processing', async () => {
    const service = RazorpayWebhookService.getInstance();
    mockPool.query.mockResolvedValueOnce({
      rows: [makeWebhookRow({ eventId: 'evt_new_123', eventType: 'payment.captured' })],
      rowCount: 1,
    });

    const result = await service.recordWebhookEventStart(
      'test',
      'evt_new_123',
      'payment.captured',
      makeWebhookPayload('payment.captured')
    );

    expect(result).toMatchObject({
      shouldProcess: true,
      row: {
        eventId: 'evt_new_123',
        processingStatus: 'pending',
      },
    });
    expect(mockPool.query).toHaveBeenCalledTimes(1);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /INSERT INTO payments\.webhook_events[\s\S]*ON CONFLICT \(provider, environment, provider_event_id\) DO NOTHING/i
      ),
      [
        'razorpay',
        'test',
        'evt_new_123',
        'payment.captured',
        false,
        null,
        null,
        null,
        expect.objectContaining({
          event: 'payment.captured',
        }),
      ]
    );
  });

  it('does not reprocess fresh pending Razorpay webhook duplicates', async () => {
    const service = RazorpayWebhookService.getInstance();
    mockPool.query
      .mockResolvedValueOnce({ rows: [], rowCount: 0 })
      .mockResolvedValueOnce({ rows: [], rowCount: 0 })
      .mockResolvedValueOnce({
        rows: [
          makeWebhookRow({
            eventId: 'evt_pending_123',
            eventType: 'payment.captured',
            processingStatus: 'pending',
          }),
        ],
        rowCount: 1,
      });

    const result = await service.recordWebhookEventStart(
      'test',
      'evt_pending_123',
      'payment.captured',
      makeWebhookPayload('payment.captured')
    );

    expect(result).toMatchObject({
      shouldProcess: false,
      row: {
        eventId: 'evt_pending_123',
        processingStatus: 'pending',
      },
    });
    expect(mockPool.query).toHaveBeenCalledTimes(3);
    expect(mockPool.query).toHaveBeenNthCalledWith(
      2,
      expect.stringMatching(
        /UPDATE payments\.webhook_events[\s\S]*processing_status = 'failed'[\s\S]*OR \(processing_status = 'pending' AND updated_at < \$5\)/i
      ),
      [
        'razorpay',
        'test',
        'evt_pending_123',
        expect.objectContaining({
          event: 'payment.captured',
        }),
        expect.any(Date),
      ]
    );
  });

  it('reclaims failed or stale Razorpay webhook events for retry', async () => {
    const service = RazorpayWebhookService.getInstance();
    mockPool.query.mockResolvedValueOnce({ rows: [], rowCount: 0 }).mockResolvedValueOnce({
      rows: [
        makeWebhookRow({
          eventId: 'evt_retry_123',
          eventType: 'payment.captured',
          processingStatus: 'pending',
          attemptCount: 2,
          lastError: null,
        }),
      ],
      rowCount: 1,
    });

    const result = await service.recordWebhookEventStart(
      'test',
      'evt_retry_123',
      'payment.captured',
      makeWebhookPayload('payment.captured')
    );

    expect(result).toMatchObject({
      shouldProcess: true,
      row: {
        eventId: 'evt_retry_123',
        processingStatus: 'pending',
        attemptCount: 2,
      },
    });
    expect(mockPool.query).toHaveBeenCalledTimes(2);
  });

  it('materializes invoice-only events into Razorpay transactions', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'invoice.expired',
      contains: ['invoice'],
      payload: {
        invoice: {
          entity: {
            id: 'inv_123',
            entity: 'invoice',
            type: 'invoice',
            description: null,
            customer_id: 'cust_123',
            customer_details: {
              id: 'cust_123',
              name: 'Buyer',
              email: 'buyer@example.com',
              contact: null,
            },
            order_id: 'order_123',
            subscription_id: null,
            payment_id: null,
            status: 'expired',
            amount: 5000,
            amount_paid: 0,
            amount_due: 5000,
            currency: 'INR',
            short_url: null,
            notes: {
              insforge_subject_type: 'team',
              insforge_subject_id: 'team_123',
            },
            line_items: [
              {
                id: 'line_123',
                item_id: 'item_123',
                name: 'Setup fee',
                description: null,
                amount: 5000,
                unit_amount: 5000,
                quantity: 1,
                currency: 'INR',
              },
            ],
            paid_at: null,
            cancelled_at: null,
            expired_at: 1780617600,
            issued_at: 1780531200,
            created_at: 1780531200,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/WITH refs[\s\S]*INSERT INTO payments\.transactions/i),
      expect.arrayContaining([
        'test',
        'invoice',
        'inv_123',
        'failed_payment',
        'failed',
        'team',
        'team_123',
        'cust_123',
        'buyer@example.com',
        5000,
        'inr',
        'Setup fee',
      ])
    );
  });

  it('preserves invoice context when invoice events include a payment entity', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'invoice.paid',
      contains: ['invoice', 'payment', 'subscription'],
      payload: {
        invoice: {
          entity: {
            id: 'inv_123',
            entity: 'invoice',
            type: 'invoice',
            description: 'Subscription invoice',
            customer_id: 'cust_123',
            customer_details: {
              id: 'cust_123',
              name: 'Buyer',
              email: 'buyer@example.com',
              contact: null,
            },
            order_id: 'order_123',
            subscription_id: 'sub_123',
            payment_id: 'pay_123',
            status: 'paid',
            amount: 5000,
            amount_paid: 5000,
            amount_due: 0,
            currency: 'INR',
            short_url: null,
            notes: {
              insforge_subject_type: 'team',
              insforge_subject_id: 'team_123',
            },
            line_items: [
              {
                id: 'line_123',
                item_id: 'item_123',
                name: 'Pro monthly',
                description: null,
                amount: 5000,
                unit_amount: 5000,
                quantity: 1,
                currency: 'INR',
              },
            ],
            paid_at: 1780617600,
            cancelled_at: null,
            expired_at: null,
            issued_at: 1780531200,
            created_at: 1780531200,
          },
        },
        payment: {
          entity: {
            id: 'pay_123',
            entity: 'payment',
            amount: 5000,
            currency: 'INR',
            status: 'captured',
            order_id: null,
            invoice_id: null,
            international: false,
            method: 'card',
            amount_refunded: 0,
            refund_status: null,
            captured: true,
            description: null,
            card_id: null,
            bank: null,
            wallet: null,
            vpa: null,
            email: 'buyer@example.com',
            contact: null,
            customer_id: 'cust_123',
            notes: {},
            fee: null,
            tax: null,
            error_code: null,
            error_description: null,
            error_source: null,
            error_step: null,
            error_reason: null,
            created_at: 1780617600,
          },
        },
        subscription: {
          entity: {
            id: 'sub_123',
            entity: 'subscription',
            plan_id: 'plan_123',
            customer_id: 'cust_123',
            status: 'active',
            current_start: 1780617600,
            current_end: 1783209600,
            ended_at: null,
            quantity: 1,
            notes: {},
            charge_at: null,
            start_at: null,
            end_at: null,
            total_count: 12,
            auth_attempts: 0,
            paid_count: 1,
            remaining_count: 11,
            short_url: null,
            has_scheduled_changes: false,
            change_scheduled_at: null,
            offer_id: null,
            created_at: 1780531200,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    const transactionCall = mockPool.query.mock.calls.find(([sql]) =>
      /WITH refs[\s\S]*INSERT INTO payments\.transactions/i.test(String(sql))
    );
    expect(transactionCall).toBeDefined();

    const transactionParams = transactionCall?.[1];
    expect(Array.isArray(transactionParams)).toBe(true);
    const params = transactionParams as unknown[];
    expect(params).toEqual(
      expect.arrayContaining([
        'test',
        'payment',
        'pay_123',
        'subscription_invoice',
        'succeeded',
        'team',
        'team_123',
        'cust_123',
        'buyer@example.com',
        5000,
        'inr',
        'Subscription invoice',
      ])
    );
    expect(JSON.parse(String(params[11]))).toEqual(
      expect.objectContaining({
        payment: 'pay_123',
        invoice: 'inv_123',
        order: 'order_123',
        subscription: 'sub_123',
      })
    );
  });

  it('preserves existing subscription invoice context when later payment events are sparse', async () => {
    const service = RazorpayWebhookService.getInstance();
    mockPool.query.mockImplementation(async (sql: string) => {
      if (/SELECT\s+type,\s+subject_type AS "subjectType"/i.test(sql)) {
        return {
          rows: [
            {
              type: 'subscription_invoice',
              subjectType: 'team',
              subjectId: 'team_123',
              providerCustomerId: 'cust_123',
              customerEmailSnapshot: 'buyer@example.com',
              relatedObjectIds: {
                payment: 'pay_123',
                invoice: 'inv_123',
                order: 'order_123',
                subscription: 'sub_123',
              },
              description: 'Pro monthly',
            },
          ],
          rowCount: 1,
        };
      }

      return { rows: [], rowCount: 0 };
    });

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'payment.captured',
      contains: ['payment'],
      payload: {
        payment: {
          entity: {
            id: 'pay_123',
            entity: 'payment',
            amount: 5000,
            currency: 'INR',
            status: 'captured',
            order_id: null,
            invoice_id: null,
            international: false,
            method: 'card',
            amount_refunded: 0,
            refund_status: null,
            captured: true,
            description: null,
            card_id: null,
            bank: null,
            wallet: null,
            vpa: null,
            email: null,
            contact: null,
            customer_id: null,
            notes: {},
            fee: null,
            tax: null,
            error_code: null,
            error_description: null,
            error_source: null,
            error_step: null,
            error_reason: null,
            created_at: 1780617600,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    const transactionCall = mockPool.query.mock.calls.find(([sql]) =>
      /WITH refs[\s\S]*INSERT INTO payments\.transactions/i.test(String(sql))
    );
    expect(transactionCall).toBeDefined();

    const params = transactionCall?.[1] as unknown[];
    expect(params).toEqual(
      expect.arrayContaining([
        'test',
        'payment',
        'pay_123',
        'subscription_invoice',
        'succeeded',
        'team',
        'team_123',
        'cust_123',
        'buyer@example.com',
        5000,
        'inr',
        'Pro monthly',
      ])
    );
    expect(JSON.parse(String(params[11]))).toEqual(
      expect.objectContaining({
        payment: 'pay_123',
        invoice: 'inv_123',
        order: 'order_123',
        subscription: 'sub_123',
      })
    );
  });

  it('records Razorpay refund creation as pending without incrementing blindly', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'refund.created',
      contains: ['refund'],
      payload: {
        refund: {
          entity: {
            id: 'rfnd_123',
            entity: 'refund',
            payment_id: 'pay_123',
            amount: 1200,
            currency: 'INR',
            status: 'pending',
            created_at: 1780617600,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.transactions/i),
      expect.arrayContaining([
        'test',
        'refund',
        'rfnd_123',
        'payment',
        'pay_123',
        'pending',
        1200,
        1200,
        'inr',
      ])
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /WITH refund_totals[\s\S]*UPDATE payments\.transactions original[\s\S]*related_object_ids = original_context\.related_object_ids \|\| refund\.related_object_ids/i
      ),
      ['test', 'pay_123']
    );
    const executedSql = mockPool.query.mock.calls.map(([sql]) => String(sql)).join('\n');
    expect(executedSql).not.toContain('amount_refunded = COALESCE(amount_refunded, 0) +');
    expect(executedSql).toContain("AND $7 = 'pending'");
  });

  it('records partially refunded Razorpay payments as partially_refunded', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'payment.captured',
      contains: ['payment'],
      payload: {
        payment: {
          entity: {
            id: 'pay_partial_123',
            entity: 'payment',
            amount: 5000,
            currency: 'INR',
            status: 'captured',
            order_id: 'order_123',
            invoice_id: null,
            international: false,
            method: 'card',
            amount_refunded: 1200,
            refund_status: 'partial',
            captured: true,
            description: null,
            card_id: null,
            bank: null,
            wallet: null,
            vpa: null,
            email: 'buyer@example.com',
            contact: null,
            customer_id: 'cust_123',
            notes: {
              insforge_subject_type: 'team',
              insforge_subject_id: 'team_123',
            },
            fee: null,
            tax: null,
            error_code: null,
            error_description: null,
            error_source: null,
            error_step: null,
            error_reason: null,
            created_at: 1780617600,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/WITH refs[\s\S]*INSERT INTO payments\.transactions/i),
      expect.arrayContaining([
        'test',
        'payment',
        'pay_partial_123',
        'one_time_payment',
        'partially_refunded',
        'team',
        'team_123',
        'cust_123',
        'buyer@example.com',
        5000,
        1200,
        'inr',
      ])
    );
    const transactionSql = mockPool.query.mock.calls.find(([sql]) =>
      /WITH refs[\s\S]*INSERT INTO payments\.transactions/i.test(String(sql))
    )?.[0];
    expect(String(transactionSql)).toMatch(
      /ORDER BY\s+CASE WHEN tx\.provider_object_type = \$2 AND tx\.provider_object_id = \$3 THEN 0 ELSE 1 END,\s+tx\.created_at DESC/i
    );
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /UPDATE payments\.razorpay_orders[\s\S]*SET status = CASE[\s\S]*WHEN \$3 THEN 'paid'[\s\S]*WHEN status = 'paid' THEN status[\s\S]*ELSE 'attempted'[\s\S]*verified_payment_id = CASE[\s\S]*WHEN \$3 THEN COALESCE\(verified_payment_id, \$5\)[\s\S]*ELSE verified_payment_id/i
      ),
      expect.arrayContaining(['test', 'order_123', true, 5000, 'pay_partial_123'])
    );
  });

  it('does not mark a Razorpay order verified from a non-succeeded payment', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'payment.failed',
      contains: ['payment'],
      payload: {
        payment: {
          entity: {
            id: 'pay_failed_123',
            entity: 'payment',
            amount: 5000,
            currency: 'INR',
            status: 'failed',
            order_id: 'order_123',
            invoice_id: null,
            international: false,
            method: 'card',
            amount_refunded: 0,
            refund_status: null,
            captured: false,
            description: null,
            card_id: null,
            bank: null,
            wallet: null,
            vpa: null,
            email: 'buyer@example.com',
            contact: null,
            customer_id: 'cust_123',
            notes: {},
            fee: null,
            tax: null,
            error_code: 'BAD_REQUEST_ERROR',
            error_description: 'Payment failed',
            error_source: 'customer',
            error_step: 'payment_authentication',
            error_reason: 'payment_failed',
            created_at: 1780617600,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /UPDATE payments\.razorpay_orders[\s\S]*SET status = CASE[\s\S]*WHEN \$3 THEN 'paid'[\s\S]*WHEN status = 'paid' THEN status[\s\S]*ELSE 'attempted'[\s\S]*verified_payment_id = CASE[\s\S]*WHEN \$3 THEN COALESCE\(verified_payment_id, \$5\)[\s\S]*ELSE verified_payment_id/i
      ),
      expect.arrayContaining(['test', 'order_123', false, 5000, 'pay_failed_123'])
    );
  });

  it('preserves a paid Razorpay order when a late authorized payment event arrives', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'payment.authorized',
      contains: ['payment'],
      payload: {
        payment: {
          entity: {
            id: 'pay_authorized_123',
            entity: 'payment',
            amount: 5000,
            currency: 'INR',
            status: 'authorized',
            order_id: 'order_123',
            invoice_id: null,
            international: false,
            method: 'card',
            amount_refunded: 0,
            refund_status: null,
            captured: false,
            description: null,
            card_id: null,
            bank: null,
            wallet: null,
            vpa: null,
            email: 'buyer@example.com',
            contact: null,
            customer_id: 'cust_123',
            notes: {},
            fee: null,
            tax: null,
            error_code: null,
            error_description: null,
            error_source: null,
            error_step: null,
            error_reason: null,
            created_at: 1780617600,
          },
        },
      },
      created_at: 1780617600,
    });

    expect(handled).toBe(true);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(
        /UPDATE payments\.razorpay_orders[\s\S]*SET status = CASE[\s\S]*WHEN \$3 THEN 'paid'[\s\S]*WHEN status = 'paid' THEN status[\s\S]*ELSE 'attempted'/i
      ),
      expect.arrayContaining(['test', 'order_123', false, 5000, 'pay_authorized_123'])
    );
  });

  it('marks Razorpay refunds as refunded only after refund.processed', async () => {
    const service = RazorpayWebhookService.getInstance();

    const handled = await getServiceInternals(service).applyRazorpayWebhookEvent('test', {
      entity: 'event',
      account_id: 'acc_123',
      event: 'refund.processed',
      contains: ['refund'],
      payload: {
        refund: {
          entity: {
            id: 'rfnd_123',
            entity: 'refund',
            payment_id: 'pay_123',
            amount: 1200,
            currency: 'INR',
            status: 'processed',
            created_at: 1780617600,
            processed_at: 1780617900,
          },
        },
      },
      created_at: 1780617900,
    });

    expect(handled).toBe(true);
    expect(mockPool.query).toHaveBeenCalledWith(
      expect.stringMatching(/INSERT INTO payments\.transactions/i),
      expect.arrayContaining([
        'test',
        'refund',
        'rfnd_123',
        'payment',
        'pay_123',
        'refunded',
        1200,
        1200,
        'inr',
      ])
    );
  });
});
