/**
 * Integration tests for PayPal webhook handler
 *
 * Route: POST /v1/webhooks/paypal
 *
 * Coverage:
 *  - Signature verification (valid, invalid, missing headers)
 *  - Idempotency (sequential replay, concurrent replay via Promise.all)
 *  - BILLING.SUBSCRIPTION.ACTIVATED — session-backed path
 *  - BILLING.SUBSCRIPTION.ACTIVATED — legacy email-lookup path
 *  - BILLING.SUBSCRIPTION.ACTIVATED — unknown identity (no-op)
 *  - BILLING.SUBSCRIPTION.CANCELLED
 *  - BILLING.SUBSCRIPTION.SUSPENDED
 *  - BILLING.SUBSCRIPTION.UPDATED
 *  - PAYMENT.SALE.COMPLETED (first delivery + duplicate paypal_payment_id)
 *  - PAYMENT.SALE.REFUNDED
 *  - CheckoutSession: pending → paid on ACTIVATED; idempotent on second distinct event
 */

// ---------------------------------------------------------------------------
// Shared Prisma mock — must be declared BEFORE any imports that pull in the
// production modules, so the factory closure captures this single instance.
// ---------------------------------------------------------------------------
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended';
import { BillingCycle } from '@prisma/client';
import type { PrismaClient } from '@prisma/client';

const sharedPrisma: DeepMockProxy<PrismaClient> = mockDeep<PrismaClient>();

jest.mock('@prisma/client', () => {
  const actual = jest.requireActual('@prisma/client');
  return {
    // Re-export every real export (enums, types, etc.)
    ...actual,
    // Override the constructor so every `new PrismaClient()` — including those
    // inside webhookIdempotency.ts, webhooks.ts, and paypalService.ts — shares
    // the same mock instance.
    PrismaClient: jest.fn(() => sharedPrisma),
  };
});

// ---------------------------------------------------------------------------
// Mock paypalCert — controls whether the signature check passes or fails.
// ---------------------------------------------------------------------------
const mockVerifyPayPalWebhookSignature = jest.fn<Promise<boolean>, []>();

jest.mock('../../../src/utils/paypalCert', () => ({
  ...jest.requireActual('../../../src/utils/paypalCert'),
  verifyPayPalWebhookSignature: mockVerifyPayPalWebhookSignature,
}));

// ---------------------------------------------------------------------------
// Mock tierService — avoid cascading into the real DB-backed cache.
// ---------------------------------------------------------------------------
const mockGetCreditAllocation = jest.fn<Promise<number>, [string, string]>();

jest.mock('../../../src/services/tierService', () => ({
  tierService: {
    getCreditAllocation: mockGetCreditAllocation,
  },
}));

// ---------------------------------------------------------------------------
// Mock paypalService — avoids real PayPal API calls and getPayPalConfig()
// cascade. Only parsePlanId is used by webhooks.ts, so we mock the whole
// module with a stub. The implementation is re-applied in beforeEach because
// jest.config.js sets resetMocks:true which clears implementations between tests.
// ---------------------------------------------------------------------------
jest.mock('../../../src/services/paypalService', () => ({
  parsePlanId: jest.fn(),
}));

jest.mock('../../../src/services/emailService', () => ({
  sendVerificationEmail: jest.fn(),
  sendPasswordResetEmail: jest.fn(),
}));

// ---------------------------------------------------------------------------
// Mock bull — prevents Bull Queue from trying to open real Redis connections
// at module-load time (src/queue.ts is imported transitively via server.ts).
// ---------------------------------------------------------------------------
jest.mock('bull', () => {
  return jest.fn().mockImplementation(() => ({
    process: jest.fn(),
    add: jest.fn().mockResolvedValue({}),
    on: jest.fn(),
    close: jest.fn().mockResolvedValue(undefined),
  }));
});

// ---------------------------------------------------------------------------
// Now import app and supertest (after mocks are registered).
// ---------------------------------------------------------------------------
import supertest from 'supertest';
import { createServer } from '../../../src/server';
import { parsePlanId as mockParsePlanId } from '../../../src/services/paypalService';

// ---------------------------------------------------------------------------
// Test data constants
// ---------------------------------------------------------------------------
const USER_ID    = '11111111-1111-4111-8111-111111111111';
const SESSION_ID = '22222222-2222-4222-8222-222222222222';
const SUB_ID     = 'I-TESTSUBSCRIPTION001';
const PLAN_ID    = 'P-STARTER-MONTHLY-TEST';
const PAYMENT_ID = 'PAYID-TEST-001';
const SALE_ID    = 'SALEID-TEST-001';
const REFUND_ID  = 'REFUNDID-TEST-001';

/** Minimal PayPal signature headers that satisfy the header-presence check. */
const VALID_PAYPAL_HEADERS = {
  'paypal-transmission-id':   'test-transmission-id',
  'paypal-transmission-time': new Date().toISOString(),
  'paypal-transmission-sig':  'dGVzdC1zaWduYXR1cmU=',
  'paypal-cert-url':          'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-TEST',
  'paypal-auth-algo':         'SHA256withRSA',
};

/**
 * Build a PayPalWebhookEvent fixture.
 * `overrides.resource` is merged with the default resource object.
 */
function makePayPalEvent(
  eventType: string,
  overrides: {
    id?: string;
    resource?: Record<string, unknown>;
  } = {}
): Record<string, unknown> {
  return {
    id:            overrides.id ?? `EVT-${eventType.replace(/\./g, '-')}-001`,
    event_type:    eventType,
    event_version: '1.0',
    create_time:   new Date().toISOString(),
    resource_type: 'subscription',
    summary:       `${eventType} event`,
    resource: {
      id:       SUB_ID,
      plan_id:  PLAN_ID,
      status:   'ACTIVE',
      subscriber: {
        email_address: 'user@example.com',
        payer_id:      'PAYERID001',
      },
      ...(overrides.resource ?? {}),
    },
  };
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/**
 * Stub $transaction to execute the provided callback with the shared mock
 * acting as the transaction proxy. Without this every transactional block
 * is a silent no-op.
 */
function enableTransaction(): void {
  sharedPrisma.$transaction.mockImplementation(async (cb: any) => cb(sharedPrisma));
}

/**
 * A minimal Subscription row fixture returned by findUnique mocks.
 */
const makeSubscriptionRow = (overrides: Partial<Record<string, unknown>> = {}) => ({
  id:                      'sub-row-uuid-001',
  user_id:                 USER_ID,
  plugin:                  'translate',
  plan_tier:               'starter',
  billing_cycle:           'monthly',
  status:                  'active',
  paypal_subscription_id:  SUB_ID,
  current_period_start:    new Date(),
  current_period_end:      new Date(Date.now() + 30 * 24 * 3600 * 1000),
  cancel_at_period_end:    false,
  customer_cost_per_char:  null,
  created_at:              new Date(),
  updated_at:              new Date(),
  ...overrides,
});

/**
 * A minimal Payment row fixture.
 */
const makePaymentRow = (overrides: Partial<Record<string, unknown>> = {}) => ({
  id:               'payment-row-uuid-001',
  user_id:          USER_ID,
  paypal_payment_id: SALE_ID,
  amount:           9.99,
  currency:         'USD',
  status:           'completed',
  type:             'subscription_payment',
  subscription_id:  'sub-row-uuid-001',
  retry_count:      0,
  error_details:    null,
  raw_webhook_payload: null,
  created_at:       new Date(),
  ...overrides,
});

// ---------------------------------------------------------------------------
// Suite
// ---------------------------------------------------------------------------

describe('POST /v1/webhooks/paypal', () => {
  let app: ReturnType<typeof createServer>;
  let agent: supertest.Agent;

  beforeAll(() => {
    app   = createServer();
    agent = supertest(app);
  });

  beforeEach(() => {
    // Reset all mock state on the shared instance.
    mockReset(sharedPrisma);

    // Reset module-level mocks.
    mockVerifyPayPalWebhookSignature.mockReset();
    mockGetCreditAllocation.mockReset();

    // Default: signature verifies OK, credits = 100 000.
    mockVerifyPayPalWebhookSignature.mockResolvedValue(true);
    mockGetCreditAllocation.mockResolvedValue(100_000);

    // Re-apply parsePlanId implementation — resetMocks:true in jest.config.js clears it.
    (mockParsePlanId as jest.MockedFunction<typeof mockParsePlanId>).mockImplementation(
      async (planId: string) => {
        const planMap: Record<string, { planTier: string; billingCycle: BillingCycle }> = {
          'P-STARTER-MONTHLY-TEST':  { planTier: 'starter',       billingCycle: 'monthly' },
          'P-STARTER-ANNUAL-TEST':   { planTier: 'starter',       billingCycle: 'annual'  },
          'P-PRO-MONTHLY-TEST':      { planTier: 'professional',   billingCycle: 'monthly' },
          'P-PRO-ANNUAL-TEST':       { planTier: 'professional',   billingCycle: 'annual'  },
          'P-ENT-MONTHLY-TEST':      { planTier: 'enterprise',     billingCycle: 'monthly' },
          'P-ENT-ANNUAL-TEST':       { planTier: 'enterprise',     billingCycle: 'annual'  },
        };
        const result = planMap[planId];
        if (!result) throw new Error(`Unknown PayPal plan ID: ${planId}`);
        return result;
      }
    );

    // Default: $transaction executes callback immediately.
    enableTransaction();

    // Default: payPalEvent.create succeeds (new event, not a duplicate).
    sharedPrisma.payPalEvent.create.mockResolvedValue({} as any);

    // Default: creditTransaction.findFirst returns null (zero balance).
    sharedPrisma.creditTransaction.findFirst.mockResolvedValue(null);
    sharedPrisma.$queryRaw.mockResolvedValue([{ last_number: 1 }] as any);
    sharedPrisma.invoice.create.mockResolvedValue({} as any);
  });

  // =========================================================================
  // 1. SIGNATURE VERIFICATION
  // =========================================================================

  describe('Signature verification', () => {
    it('returns 200 when signature is valid', async () => {
      const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED');

      // Provide enough stubs for ACTIVATED to resolve without error.
      sharedPrisma.checkoutSession.findUnique.mockResolvedValue(null);
      sharedPrisma.user.findUnique.mockResolvedValue({
        id: USER_ID, email: 'user@example.com',
      } as any);
      sharedPrisma.subscription.upsert.mockResolvedValue({} as any);
      sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);
      sharedPrisma.user.update.mockResolvedValue({} as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);
      expect(res.body.received).toBe(true);
    });

    it('returns 401 when signature verification fails', async () => {
      mockVerifyPayPalWebhookSignature.mockResolvedValue(false);

      const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED');

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(401);
      expect(res.body.error.code).toBe('INVALID_SIGNATURE');
    });

    it('returns 401 when signature verification fails due to tampered body', async () => {
      // Simulate what verifyPayPalWebhookSignature would return for a tampered payload.
      mockVerifyPayPalWebhookSignature.mockResolvedValue(false);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send({ id: 'TAMPERED', event_type: 'BILLING.SUBSCRIPTION.ACTIVATED', resource: {} });

      expect(res.status).toBe(401);
    });

    it('returns 401 and makes no DB calls when headers are missing', async () => {
      // verifyPayPalWebhookSignature is called with empty headers; the real
      // implementation (and our mock configured to return false) must reject.
      mockVerifyPayPalWebhookSignature.mockResolvedValue(false);

      const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED');

      // POST without any paypal-* headers.
      const res = await agent
        .post('/v1/webhooks/paypal')
        .send(event);

      expect(res.status).toBe(401);
      // No DB side-effects.
      expect(sharedPrisma.payPalEvent.create).not.toHaveBeenCalled();
      expect(sharedPrisma.subscription.upsert).not.toHaveBeenCalled();
    });
  });

  // =========================================================================
  // 2. IDEMPOTENCY
  // =========================================================================

  describe('Idempotency', () => {
    /**
     * Uses PAYMENT.SALE.COMPLETED to prove the headline guarantee:
     * credits are not allocated twice when the same event is delivered twice.
     */
    it('returns { duplicate: true } on sequential replay and credits are allocated only once', async () => {
      const event = makePayPalEvent('PAYMENT.SALE.COMPLETED', {
        id: 'EVT-IDEMPOTENCY-SEQ-001',
        resource: {
          id:                   PAYMENT_ID,
          billing_agreement_id: SUB_ID,
          amount: { total: '9.99', currency: 'USD' },
        },
      });

      const sub = makeSubscriptionRow();
      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      sharedPrisma.payment.findUnique.mockResolvedValue(null); // no pre-existing payment
      sharedPrisma.payment.create.mockResolvedValue({} as any);
      sharedPrisma.subscription.update.mockResolvedValue(sub as any);
      sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);

      // First delivery — PayPalEvent insert succeeds.
      sharedPrisma.payPalEvent.create.mockResolvedValueOnce({} as any);

      const first = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(first.status).toBe(200);
      expect(first.body.duplicate).toBeUndefined();
      // Credits allocated on the first delivery.
      expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledTimes(1);

      // Clear call history so we can assert zero calls on second delivery.
      sharedPrisma.creditTransaction.create.mockClear();
      sharedPrisma.payment.create.mockClear();

      // Second delivery — unique constraint violation on paypal_events.
      const p2002Error = Object.assign(new Error('Unique constraint failed'), { code: 'P2002' });
      sharedPrisma.payPalEvent.create.mockRejectedValueOnce(p2002Error);

      const second = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(second.status).toBe(200);
      expect(second.body.duplicate).toBe(true);

      // No credits must be allocated on replay — this is the headline guarantee.
      expect(sharedPrisma.creditTransaction.create).not.toHaveBeenCalled();
      expect(sharedPrisma.payment.create).not.toHaveBeenCalled();
    });

    it('credits are allocated exactly once on concurrent replay (Promise.all)', async () => {
      const event = makePayPalEvent('PAYMENT.SALE.COMPLETED', {
        id: 'EVT-CONCURRENT-001',
        resource: {
          id:                   PAYMENT_ID,
          billing_agreement_id: SUB_ID,
          amount: { total: '9.99', currency: 'USD' },
        },
      });

      const sub = makeSubscriptionRow();
      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      sharedPrisma.payment.findUnique.mockResolvedValue(null);
      sharedPrisma.payment.create.mockResolvedValue({} as any);
      sharedPrisma.subscription.update.mockResolvedValue(sub as any);
      sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);

      // First concurrent call succeeds; every subsequent call gets P2002.
      let createCallCount = 0;
      const p2002Error = Object.assign(new Error('Unique constraint failed'), { code: 'P2002' });
      (sharedPrisma.payPalEvent.create as jest.MockedFunction<any>).mockImplementation(async () => {
        createCallCount += 1;
        if (createCallCount > 1) throw p2002Error;
        return {} as any;
      });

      const [res1, res2] = await Promise.all([
        agent.post('/v1/webhooks/paypal').set(VALID_PAYPAL_HEADERS).send(event),
        agent.post('/v1/webhooks/paypal').set(VALID_PAYPAL_HEADERS).send(event),
      ]);

      // Exactly one of the two responses is a duplicate.
      const results = [res1, res2];
      const dupCount = results.filter((r) => r.body.duplicate === true).length;
      const okCount  = results.filter((r) => r.body.received === true && !r.body.duplicate).length;

      expect(dupCount).toBe(1);
      expect(okCount).toBe(1);

      // Credits allocated exactly once — this is the ACID guarantee we're proving.
      expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledTimes(1);
    });
  });

  // =========================================================================
  // 3. BILLING.SUBSCRIPTION.ACTIVATED — session-backed path
  // =========================================================================

  describe('BILLING.SUBSCRIPTION.ACTIVATED', () => {
    describe('session-backed path (custom_id present)', () => {
      it('creates subscription with session plugin and allocates credits', async () => {
        const sessionRow = {
          id:        SESSION_ID,
          user_id:   USER_ID,
          plugin:    'international',
          status:    'pending',
          expires_at: new Date(Date.now() + 3600_000),
        };

        const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED', {
          resource: {
            id:          SUB_ID,
            plan_id:     PLAN_ID,
            custom_id:   SESSION_ID,
            subscriber: { email_address: 'user@example.com' },
          },
        });

        // custom_id lookup finds the session.
        sharedPrisma.checkoutSession.findUnique.mockResolvedValueOnce(sessionRow as any);
        sharedPrisma.checkoutSession.updateMany.mockResolvedValue({ count: 1 });
        sharedPrisma.subscription.upsert.mockResolvedValue({} as any);
        sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);
        sharedPrisma.user.update.mockResolvedValue({} as any);

        const res = await agent
          .post('/v1/webhooks/paypal')
          .set(VALID_PAYPAL_HEADERS)
          .send(event);

        expect(res.status).toBe(200);

        // Subscription upsert must use plugin from session, not hardcoded 'translate'.
        expect(sharedPrisma.subscription.upsert).toHaveBeenCalledWith(
          expect.objectContaining({
            where: { user_id_plugin: { user_id: USER_ID, plugin: 'international' } },
          })
        );

        // Credits allocated once from the session plugin's tier.
        expect(mockParsePlanId).toHaveBeenCalledWith(PLAN_ID, 'international');
        expect(mockGetCreditAllocation).toHaveBeenCalledWith('starter', 'international');
        expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledTimes(1);
        expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledWith(
          expect.objectContaining({
            data: expect.objectContaining({
              user_id: USER_ID,
              type:    'allocation',
              amount:  100_000,
            }),
          })
        );

        // Session flipped to paid.
        expect(sharedPrisma.checkoutSession.updateMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: { id: SESSION_ID, status: 'pending' },
            data:  expect.objectContaining({ status: 'paid' }),
          })
        );
      });

      it('session status is flipped to paid and stays paid on second distinct ACTIVATED event', async () => {
        // First activation (event A).
        const sessionRow = {
          id: SESSION_ID, user_id: USER_ID, plugin: 'translate', status: 'pending',
          expires_at: new Date(Date.now() + 3600_000),
        };

        const makeActivatedEvent = (eventId: string) =>
          makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED', {
            id: eventId,
            resource: {
              id:        SUB_ID,
              plan_id:   PLAN_ID,
              custom_id: SESSION_ID,
              subscriber: { email_address: 'user@example.com' },
            },
          });

        // Stub for first event.
        sharedPrisma.checkoutSession.findUnique.mockResolvedValue(sessionRow as any);
        sharedPrisma.checkoutSession.updateMany.mockResolvedValue({ count: 1 });
        sharedPrisma.subscription.upsert.mockResolvedValue({} as any);
        sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);
        sharedPrisma.user.update.mockResolvedValue({} as any);

        await agent
          .post('/v1/webhooks/paypal')
          .set(VALID_PAYPAL_HEADERS)
          .send(makeActivatedEvent('EVT-ACTIVATED-001'));

        // Reset for second event (different event.id, same subscription).
        mockReset(sharedPrisma);
        enableTransaction();
        sharedPrisma.payPalEvent.create.mockResolvedValue({} as any);
        sharedPrisma.creditTransaction.findFirst.mockResolvedValue(null);
        mockGetCreditAllocation.mockResolvedValue(100_000);

        // Session is now 'paid'; updateMany WHERE status='pending' won't match.
        const paidSession = { ...sessionRow, status: 'paid' };
        sharedPrisma.checkoutSession.findUnique.mockResolvedValue(paidSession as any);
        sharedPrisma.checkoutSession.updateMany.mockResolvedValue({ count: 0 }); // no-op
        sharedPrisma.subscription.upsert.mockResolvedValue({} as any);
        sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);
        sharedPrisma.user.update.mockResolvedValue({} as any);

        const res2 = await agent
          .post('/v1/webhooks/paypal')
          .set(VALID_PAYPAL_HEADERS)
          .send(makeActivatedEvent('EVT-ACTIVATED-002'));

        expect(res2.status).toBe(200);

        // updateMany was called but count=0 means session stayed 'paid'.
        expect(sharedPrisma.checkoutSession.updateMany).toHaveBeenCalledWith(
          expect.objectContaining({
            where: { id: SESSION_ID, status: 'pending' },
          })
        );

        // BUG PROBE: Credits must NOT be re-allocated on the second distinct ACTIVATED
        // event for the same subscription. If this assertion fails it means the handler
        // calls creditTransaction.create even when the session is already 'paid', which
        // would double-credit the user. Do NOT fix the production code — report the bug.
        expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledTimes(1);
      });
    });

    // -----------------------------------------------------------------------
    // Legacy email-lookup path
    // -----------------------------------------------------------------------
    describe('legacy email-lookup path (no session)', () => {
      it('creates subscription with plugin=translate when user found by email', async () => {
        const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED', {
          resource: {
            id:      SUB_ID,
            plan_id: PLAN_ID,
            // No custom_id — falls through to email lookup.
            subscriber: { email_address: 'user@example.com' },
          },
        });

        // Both findUnique(by id) and findUnique(by paypal_subscription_id) return null.
        sharedPrisma.checkoutSession.findUnique.mockResolvedValue(null);
        sharedPrisma.user.findUnique.mockResolvedValue({
          id: USER_ID, email: 'user@example.com',
        } as any);
        sharedPrisma.subscription.upsert.mockResolvedValue({} as any);
        sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);
        sharedPrisma.user.update.mockResolvedValue({} as any);

        const res = await agent
          .post('/v1/webhooks/paypal')
          .set(VALID_PAYPAL_HEADERS)
          .send(event);

        expect(res.status).toBe(200);

        // plugin defaults to 'translate' on the legacy path.
        expect(sharedPrisma.subscription.upsert).toHaveBeenCalledWith(
          expect.objectContaining({
            where: { user_id_plugin: { user_id: USER_ID, plugin: 'translate' } },
          })
        );
      });

      it('returns 200 with no side-effects when email is unknown', async () => {
        const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED', {
          resource: {
            id:      SUB_ID,
            plan_id: PLAN_ID,
            subscriber: { email_address: 'unknown@example.com' },
          },
        });

        sharedPrisma.checkoutSession.findUnique.mockResolvedValue(null);
        sharedPrisma.user.findUnique.mockResolvedValue(null); // unknown email

        const res = await agent
          .post('/v1/webhooks/paypal')
          .set(VALID_PAYPAL_HEADERS)
          .send(event);

        expect(res.status).toBe(200);
        expect(sharedPrisma.subscription.upsert).not.toHaveBeenCalled();
        expect(sharedPrisma.creditTransaction.create).not.toHaveBeenCalled();
      });

      it('returns 200 with no side-effects when no session and no email in payload', async () => {
        const event = makePayPalEvent('BILLING.SUBSCRIPTION.ACTIVATED', {
          resource: {
            id:      SUB_ID,
            plan_id: PLAN_ID,
            // subscriber omitted entirely
          },
        });

        sharedPrisma.checkoutSession.findUnique.mockResolvedValue(null);

        const res = await agent
          .post('/v1/webhooks/paypal')
          .set(VALID_PAYPAL_HEADERS)
          .send(event);

        expect(res.status).toBe(200);
        expect(sharedPrisma.subscription.upsert).not.toHaveBeenCalled();
      });
    });
  });

  // =========================================================================
  // 4. BILLING.SUBSCRIPTION.CANCELLED
  // =========================================================================

  describe('BILLING.SUBSCRIPTION.CANCELLED', () => {
    it('transitions subscription to cancelled status', async () => {
      const event = makePayPalEvent('BILLING.SUBSCRIPTION.CANCELLED', {
        resource: { id: SUB_ID },
      });

      const sub = makeSubscriptionRow();
      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      sharedPrisma.subscription.update.mockResolvedValue({ ...sub, status: 'cancelled' } as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);

      expect(sharedPrisma.subscription.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: sub.id },
          data:  expect.objectContaining({
            status:              'cancelled',
            cancel_at_period_end: true,
          }),
        })
      );
    });

    it('returns 200 without error when subscription is not found', async () => {
      const event = makePayPalEvent('BILLING.SUBSCRIPTION.CANCELLED', {
        resource: { id: 'I-NONEXISTENT' },
      });

      sharedPrisma.subscription.findUnique.mockResolvedValue(null);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);
      expect(sharedPrisma.subscription.update).not.toHaveBeenCalled();
    });
  });

  // =========================================================================
  // 5. BILLING.SUBSCRIPTION.SUSPENDED
  // =========================================================================

  describe('BILLING.SUBSCRIPTION.SUSPENDED', () => {
    it('transitions subscription to suspended status', async () => {
      const event = makePayPalEvent('BILLING.SUBSCRIPTION.SUSPENDED', {
        resource: { id: SUB_ID },
      });

      const sub = makeSubscriptionRow();
      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      sharedPrisma.subscription.update.mockResolvedValue({ ...sub, status: 'suspended' } as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);

      expect(sharedPrisma.subscription.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: sub.id },
          data:  expect.objectContaining({ status: 'suspended' }),
        })
      );
      // cancel_at_period_end must NOT be set for suspension.
      const callData = (sharedPrisma.subscription.update as jest.Mock).mock.calls[0][0].data;
      expect(callData).not.toHaveProperty('cancel_at_period_end');
    });
  });

  // =========================================================================
  // 6. BILLING.SUBSCRIPTION.UPDATED
  // =========================================================================

  describe('BILLING.SUBSCRIPTION.UPDATED', () => {
    it('updates tier and billing cycle when plan_id changes', async () => {
      const NEW_PLAN_ID = 'P-PRO-MONTHLY-TEST';

      const event = makePayPalEvent('BILLING.SUBSCRIPTION.UPDATED', {
        resource: {
          id:      SUB_ID,
          plan_id: NEW_PLAN_ID,
        },
      });

      const sub = makeSubscriptionRow();
      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      sharedPrisma.subscription.update.mockResolvedValue({
        ...sub,
        plan_tier:     'professional',
        billing_cycle: 'monthly',
      } as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);

      expect(sharedPrisma.subscription.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: sub.id },
          data:  expect.objectContaining({
            plan_tier:     'professional',
            billing_cycle: 'monthly',
          }),
        })
      );
    });

    it('returns 200 without update when subscription is not found', async () => {
      const event = makePayPalEvent('BILLING.SUBSCRIPTION.UPDATED', {
        resource: { id: 'I-NONEXISTENT', plan_id: PLAN_ID },
      });

      sharedPrisma.subscription.findUnique.mockResolvedValue(null);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);
      expect(sharedPrisma.subscription.update).not.toHaveBeenCalled();
    });
  });

  // =========================================================================
  // 7. PAYMENT.SALE.COMPLETED
  // =========================================================================

  describe('PAYMENT.SALE.COMPLETED', () => {
    it('creates Payment row, advances billing period, and allocates credits', async () => {
      const event = makePayPalEvent('PAYMENT.SALE.COMPLETED', {
        resource: {
          id:                   PAYMENT_ID,
          billing_agreement_id: SUB_ID,
          amount: {
            total:    '9.99',
            currency: 'USD',
          },
        },
      });

      const sub = makeSubscriptionRow({ plugin: 'international' });
      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      // No existing payment for this paypal_payment_id.
      sharedPrisma.payment.findUnique.mockResolvedValue(null);
      sharedPrisma.payment.create.mockResolvedValue({} as any);
      sharedPrisma.subscription.update.mockResolvedValue(sub as any);
      sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);

      // Payment row created.
      expect(sharedPrisma.payment.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            paypal_payment_id: PAYMENT_ID,
            amount:            9.99,
            currency:          'USD',
            status:            'completed',
            type:              'subscription_payment',
          }),
        })
      );

      // Billing period advanced.
      expect(sharedPrisma.subscription.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: sub.id },
          data:  expect.objectContaining({
            current_period_start: expect.any(Date),
            current_period_end:   expect.any(Date),
          }),
        })
      );

      // Credits allocated from the subscription plugin's tier.
      expect(mockGetCreditAllocation).toHaveBeenCalledWith('starter', 'international');
      expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledTimes(1);
      expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            user_id:            USER_ID,
            type:               'allocation',
            amount:             100_000,
            related_payment_id: PAYMENT_ID,
          }),
        })
      );
    });

    it('bumps retry_count and does NOT re-allocate credits on duplicate paypal_payment_id', async () => {
      const event = makePayPalEvent('PAYMENT.SALE.COMPLETED', {
        resource: {
          id:                   PAYMENT_ID,
          billing_agreement_id: SUB_ID,
          amount: { total: '9.99', currency: 'USD' },
        },
      });

      const sub            = makeSubscriptionRow();
      const existingPayment = makePaymentRow({ paypal_payment_id: PAYMENT_ID });

      sharedPrisma.subscription.findUnique.mockResolvedValue(sub as any);
      // Existing payment for this paypal_payment_id — duplicate delivery.
      sharedPrisma.payment.findUnique.mockResolvedValue(existingPayment as any);
      sharedPrisma.payment.update.mockResolvedValue({ ...existingPayment, retry_count: 1 } as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);

      // retry_count incremented.
      expect(sharedPrisma.payment.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: existingPayment.id },
          data:  { retry_count: { increment: 1 } },
        })
      );

      // No new payment row or credit allocation.
      expect(sharedPrisma.payment.create).not.toHaveBeenCalled();
      expect(sharedPrisma.creditTransaction.create).not.toHaveBeenCalled();
    });

    it('returns 200 with no side-effects when subscription is not found', async () => {
      const event = makePayPalEvent('PAYMENT.SALE.COMPLETED', {
        resource: {
          id:                   PAYMENT_ID,
          billing_agreement_id: 'I-NONEXISTENT',
          amount: { total: '9.99', currency: 'USD' },
        },
      });

      sharedPrisma.subscription.findUnique.mockResolvedValue(null);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);
      expect(sharedPrisma.payment.create).not.toHaveBeenCalled();
      expect(sharedPrisma.creditTransaction.create).not.toHaveBeenCalled();
    });
  });

  // =========================================================================
  // 8. PAYMENT.SALE.REFUNDED
  // =========================================================================

  describe('PAYMENT.SALE.REFUNDED', () => {
    it('marks original payment refunded, creates refund record, and reverses credits', async () => {
      const CREDIT_ALLOC_ID = 'credit-tx-uuid-001';

      const event = makePayPalEvent('PAYMENT.SALE.REFUNDED', {
        resource: {
          id:      REFUND_ID,
          sale_id: SALE_ID,
          amount: { total: '9.99' },
        },
      });

      const origPayment = makePaymentRow({ paypal_payment_id: SALE_ID });

      sharedPrisma.payment.findUnique.mockResolvedValue(origPayment as any);
      sharedPrisma.payment.update.mockResolvedValue({ ...origPayment, status: 'refunded' } as any);
      sharedPrisma.payment.create.mockResolvedValue({} as any);

      // Credit allocation to be reversed.
      // Handler call order inside $transaction (see webhooks.ts handlePaymentRefunded):
      //   1st findFirst: creditTransaction by { user_id, related_payment_id: saleId, type: 'allocation' }
      //   2nd findFirst: creditTransaction by { user_id } orderBy created_at desc  (current balance)
      const creditAlloc = {
        id:            CREDIT_ALLOC_ID,
        user_id:       USER_ID,
        type:          'allocation',
        amount:        100_000,
        balance_after: 100_000,
        related_payment_id: SALE_ID,
      };
      sharedPrisma.creditTransaction.findFirst
        .mockResolvedValueOnce(creditAlloc as any)              // 1st call: find allocation to reverse
        .mockResolvedValueOnce({ balance_after: 100_000 } as any); // 2nd call: current balance

      sharedPrisma.creditTransaction.create.mockResolvedValue({} as any);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);

      // Original payment marked refunded.
      expect(sharedPrisma.payment.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: origPayment.id },
          data:  { status: 'refunded' },
        })
      );

      // Refund payment record created.
      expect(sharedPrisma.payment.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            paypal_payment_id: REFUND_ID,
            type:              'refund',
            status:            'completed',
          }),
        })
      );

      // Credit reversal transaction created.
      expect(sharedPrisma.creditTransaction.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            user_id:            USER_ID,
            type:               'deduction',
            related_payment_id: REFUND_ID,
          }),
        })
      );
    });

    it('returns 200 with no side-effects when original payment is not found', async () => {
      const event = makePayPalEvent('PAYMENT.SALE.REFUNDED', {
        resource: {
          id:      REFUND_ID,
          sale_id: 'SALEID-NONEXISTENT',
          amount: { total: '9.99' },
        },
      });

      sharedPrisma.payment.findUnique.mockResolvedValue(null);

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);
      expect(sharedPrisma.payment.update).not.toHaveBeenCalled();
      expect(sharedPrisma.payment.create).not.toHaveBeenCalled();
      expect(sharedPrisma.creditTransaction.create).not.toHaveBeenCalled();
    });
  });

  // =========================================================================
  // 9. Unhandled event type
  // =========================================================================

  describe('Unhandled event type', () => {
    it('returns 200 without any DB writes for an unknown event type', async () => {
      const event = makePayPalEvent('BILLING.SUBSCRIPTION.EXPIRED', {
        resource: { id: SUB_ID },
      });

      const res = await agent
        .post('/v1/webhooks/paypal')
        .set(VALID_PAYPAL_HEADERS)
        .send(event);

      expect(res.status).toBe(200);
      expect(res.body.received).toBe(true);
      // The event was registered in paypal_events but no business logic ran.
      expect(sharedPrisma.subscription.update).not.toHaveBeenCalled();
      expect(sharedPrisma.subscription.upsert).not.toHaveBeenCalled();
    });
  });
});
