import { describe, expect, it } from 'vitest';
import { readFileSync } from 'fs';
import { resolve } from 'path';
import {
  cancelRazorpaySubscriptionBodySchema,
  createCheckoutSessionBodySchema,
  createCustomerPortalSessionBodySchema,
  createRazorpayItemBodySchema,
  createRazorpayOrderBodySchema,
  createRazorpayPlanBodySchema,
  createRazorpaySubscriptionBodySchema,
  createStripePriceBodySchema,
  createStripeProductBodySchema,
  getRazorpayWebhookSetupResponseSchema,
  listStripeCatalogQuerySchema,
  listPaymentCustomersQuerySchema,
  listPaymentTransactionsQuerySchema,
  listStripePricesQuerySchema,
  listStripeProductsQuerySchema,
  listRazorpaySubscriptionsQuerySchema,
  listStripeSubscriptionsQuerySchema,
  paymentEnvironmentParamsSchema,
  pauseRazorpaySubscriptionBodySchema,
  razorpayEnvironmentParamsSchema,
  razorpaySubscriptionParamsSchema,
  getRazorpayConfigResponseSchema,
  getRazorpayStatusResponseSchema,
  razorpayWebhookParamsSchema,
  resumeRazorpaySubscriptionBodySchema,
  syncRazorpayPaymentsResponseSchema,
  updateStripePriceBodySchema,
  updateStripeProductBodySchema,
  upsertStripeConfigBodySchema,
  upsertRazorpayConfigBodySchema,
  updateRazorpayItemBodySchema,
  verifyRazorpayOrderBodySchema,
  verifyRazorpaySubscriptionBodySchema,
} from '@insforge/shared-schemas';

const FAKE_LIVE_SECRET_KEY = 'stripe_live_secret_placeholder';

describe('payments route schemas', () => {
  const paymentsRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/index.routes.ts'),
    'utf-8'
  );
  const paymentsApiSchemaSource = readFileSync(
    resolve(__dirname, '../../../packages/shared-schemas/src/payments-api.schema.ts'),
    'utf-8'
  );
  const stripeRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/stripe/index.routes.ts'),
    'utf-8'
  );
  const stripeConfigRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/stripe/config.routes.ts'),
    'utf-8'
  );
  const stripeCatalogRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/stripe/catalog.routes.ts'),
    'utf-8'
  );
  const paymentHelpersSource = readFileSync(
    resolve(__dirname, '../../src/services/payments/helpers.ts'),
    'utf-8'
  );
  const razorpayRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/razorpay/index.routes.ts'),
    'utf-8'
  );
  const razorpayConfigRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/razorpay/config.routes.ts'),
    'utf-8'
  );
  const razorpayCatalogRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/payments/razorpay/catalog.routes.ts'),
    'utf-8'
  );
  const webhooksRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/webhooks/index.routes.ts'),
    'utf-8'
  );
  const razorpayWebhookRouteSource = readFileSync(
    resolve(__dirname, '../../src/api/routes/webhooks/razorpay.routes.ts'),
    'utf-8'
  );
  const razorpayWebhookServiceSource = readFileSync(
    resolve(__dirname, '../../src/services/payments/razorpay/webhook.service.ts'),
    'utf-8'
  );
  const razorpaySyncServiceSource = readFileSync(
    resolve(__dirname, '../../src/services/payments/razorpay/sync.service.ts'),
    'utf-8'
  );
  const razorpayConfigServiceSource = readFileSync(
    resolve(__dirname, '../../src/services/payments/razorpay/config.service.ts'),
    'utf-8'
  );
  const razorpayProviderSource = readFileSync(
    resolve(__dirname, '../../src/providers/payments/razorpay.provider.ts'),
    'utf-8'
  );

  it('keeps checkout session creation on runtime auth before environment admin routes', () => {
    const adminGuardIndex = stripeRouteSource.indexOf('environmentRouter.use(verifyAdmin)');
    expect(adminGuardIndex).toBeGreaterThan(-1);
    expect(stripeRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/checkout-sessions'[\s\S]*verifyUser[\s\S]*createCheckoutSessionBodySchema/
    );
    expect(stripeRouteSource.indexOf("'/checkout-sessions'")).toBeLessThan(adminGuardIndex);
    expect(stripeRouteSource).toContain('Checkout session creation requires a user token');
  });

  it('keeps customer portal session creation on runtime auth before environment admin routes', () => {
    const adminGuardIndex = stripeRouteSource.indexOf('environmentRouter.use(verifyAdmin)');
    expect(adminGuardIndex).toBeGreaterThan(-1);
    expect(stripeRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/customer-portal-sessions'[\s\S]*verifyUser[\s\S]*createCustomerPortalSessionBodySchema/
    );
    expect(stripeRouteSource.indexOf("'/customer-portal-sessions'")).toBeLessThan(adminGuardIndex);
    expect(stripeRouteSource).toContain('Customer portal session creation requires a user token');
  });

  it('keeps global admin config routes explicit and admin-guarded', () => {
    expect(stripeRouteSource).toMatch(/router\.get\(\s*'\/status',\s*verifyAdmin/);
    expect(stripeRouteSource).toMatch(/router\.get\(\s*'\/config',\s*verifyAdmin/);
    expect(razorpayRouteSource).toMatch(/router\.get\(\s*'\/status',\s*verifyAdmin/);
    expect(razorpayRouteSource).toMatch(/router\.get\(\s*'\/config',\s*verifyAdmin/);
    expect(paymentsRouteSource).toMatch(/router\.use\(\s*'\/stripe',\s*stripeRouter/);
    expect(stripeRouteSource).toMatch(
      /router\.post\(\s*'\/sync',\s*verifyAdmin[\s\S]*environment: 'all'/
    );
    expect(razorpayRouteSource).toMatch(
      /router\.post\(\s*'\/sync',\s*verifyAdmin[\s\S]*syncAll\('all'\)/
    );
  });

  it('mounts Stripe environment-scoped payments routes under the Stripe router', () => {
    expect(stripeRouteSource).toContain('const environmentRouter = Router({ mergeParams: true });');
    expect(stripeRouteSource).toContain("router.use('/:environment', environmentRouter)");
    expect(paymentsRouteSource).toContain("router.use('/stripe', stripeRouter)");
    expect(paymentsRouteSource).toContain("router.use('/razorpay', razorpayRouter)");
    expect(paymentsRouteSource).toContain("from './stripe/index.routes.js'");
    expect(paymentsRouteSource).toContain("from './razorpay/index.routes.js'");
    expect(paymentsRouteSource).not.toContain("router.use('/:environment'");
  });

  it('mounts Razorpay environment-scoped config routes under the Razorpay admin guard', () => {
    const adminGuardIndex = razorpayRouteSource.indexOf('environmentRouter.use(verifyAdmin)');
    expect(adminGuardIndex).toBeGreaterThan(-1);
    expect(razorpayRouteSource).toContain(
      'const environmentRouter = Router({ mergeParams: true });'
    );
    expect(
      razorpayRouteSource.indexOf('environmentRouter.use(razorpayConfigRouter)')
    ).toBeGreaterThan(adminGuardIndex);
    expect(razorpayRouteSource).toContain("router.use('/:environment', environmentRouter)");
    expect(razorpayRouteSource).toContain("from './config.routes.js'");
    expect(razorpayRouteSource).not.toContain("router.put('/:environment/config'");
    expect(razorpayRouteSource).not.toContain('paymentEnvironmentSchema');
  });

  it('mounts Razorpay admin reads under provider-specific services', () => {
    const adminGuardIndex = razorpayRouteSource.indexOf('environmentRouter.use(verifyAdmin)');
    expect(adminGuardIndex).toBeGreaterThan(-1);
    expect(
      razorpayRouteSource.indexOf("environmentRouter.use('/catalog', razorpayCatalogRouter)")
    ).toBeGreaterThan(adminGuardIndex);
    expect(razorpayRouteSource.indexOf("'/customers'")).toBeGreaterThan(adminGuardIndex);
    expect(razorpayRouteSource.indexOf("'/transactions'")).toBeGreaterThan(adminGuardIndex);
    expect(
      razorpayRouteSource.match(/environmentRouter\.get\(\s*'\/subscriptions'/)?.index
    ).toBeGreaterThan(adminGuardIndex);
    expect(razorpayCatalogRouteSource).toContain('RazorpayCatalogService');
    expect(razorpayRouteSource).toContain('RazorpaySubscriptionService');
    expect(razorpayRouteSource).toContain('PaymentTransactionService');
    expect(razorpayRouteSource).not.toContain('ProjectionService');
    expect(razorpayCatalogRouteSource).toMatch(/catalogService\.listCatalog\(environment\)/);
    expect(razorpayCatalogRouteSource).toMatch(/router\.post\(\s*'\/items'/);
    expect(razorpayCatalogRouteSource).toMatch(/router\.patch\(\s*'\/items\/:itemId'/);
    expect(razorpayCatalogRouteSource).toMatch(/router\.post\(\s*'\/plans'/);
    expect(razorpayRouteSource).toMatch(
      /customerService\.listCustomers\(\{ environment, \.\.\.query \},\s*'razorpay'\)/
    );
    expect(razorpayRouteSource).toMatch(
      /transactionService\.listTransactions\(\s*\{[\s\S]*environment,[\s\S]*\.\.\.query,[\s\S]*\},\s*'razorpay'/
    );
    expect(razorpayRouteSource).toMatch(
      /subscriptionService\.listSubscriptions\(\{ environment, \.\.\.query \}\)/
    );
  });

  it('mounts Razorpay native checkout flows under user auth before admin routes', () => {
    const adminGuardIndex = razorpayRouteSource.indexOf('environmentRouter.use(verifyAdmin)');
    expect(adminGuardIndex).toBeGreaterThan(-1);

    for (const route of [
      "'/orders'",
      "'/orders/verify'",
      "'/subscriptions'",
      "'/subscriptions/verify'",
      "'/subscriptions/:subscriptionId/cancel'",
      "'/subscriptions/:subscriptionId/pause'",
      "'/subscriptions/:subscriptionId/resume'",
    ]) {
      expect(razorpayRouteSource.indexOf(route)).toBeGreaterThan(-1);
      expect(razorpayRouteSource.indexOf(route)).toBeLessThan(adminGuardIndex);
    }

    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/orders'[\s\S]*verifyUser[\s\S]*createRazorpayOrderBodySchema/
    );
    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/orders\/verify'[\s\S]*verifyUser[\s\S]*verifyRazorpayOrderBodySchema/
    );
    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/subscriptions'[\s\S]*verifyUser[\s\S]*createRazorpaySubscriptionBodySchema/
    );
    expect(razorpayRouteSource).toMatch(
      /subscriptionService\.createSubscription\(\s*\{[\s\S]*environment,[\s\S]*\.\.\.body,[\s\S]*\},\s*req\.user\s*\)/
    );
    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/subscriptions\/verify'[\s\S]*verifyUser[\s\S]*verifyRazorpaySubscriptionBodySchema/
    );
    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/subscriptions\/:subscriptionId\/cancel'[\s\S]*verifyUser[\s\S]*cancelRazorpaySubscriptionBodySchema/
    );
    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/subscriptions\/:subscriptionId\/pause'[\s\S]*verifyUser[\s\S]*pauseRazorpaySubscriptionBodySchema/
    );
    expect(razorpayRouteSource).toMatch(
      /environmentRouter\.post\(\s*'\/subscriptions\/:subscriptionId\/resume'[\s\S]*verifyUser[\s\S]*resumeRazorpaySubscriptionBodySchema/
    );
    expect(razorpayRouteSource).toContain('RazorpayOrderService');
    expect(razorpayRouteSource).not.toContain('checkout-sessions');
    expect(razorpayRouteSource).not.toContain('payment-links');
  });

  it('keeps Razorpay route validation schemas in shared-schemas', () => {
    expect(paymentsApiSchemaSource).toContain('razorpayEnvironmentParamsSchema');
    expect(paymentsApiSchemaSource).toContain('razorpayWebhookParamsSchema');
    expect(paymentsApiSchemaSource).toContain('upsertRazorpayConfigBodySchema');
    expect(paymentsApiSchemaSource).toContain('getRazorpayWebhookSetupResponseSchema');
    expect(paymentsApiSchemaSource).toContain('rotateRazorpayWebhookSecretResponseSchema');
    expect(paymentsApiSchemaSource).toContain('getRazorpayStatusResponseSchema');
    expect(paymentsApiSchemaSource).toContain('getRazorpayConfigResponseSchema');
    expect(paymentsApiSchemaSource).toContain('syncRazorpayPaymentsResponseSchema');
    expect(paymentsApiSchemaSource).toContain('createRazorpayOrderBodySchema');
    expect(paymentsApiSchemaSource).toContain('verifyRazorpayOrderBodySchema');
    expect(paymentsApiSchemaSource).toContain('createRazorpaySubscriptionBodySchema');
    expect(paymentsApiSchemaSource).toContain('verifyRazorpaySubscriptionBodySchema');
    expect(paymentsApiSchemaSource).toContain('cancelRazorpaySubscriptionBodySchema');
    expect(paymentsApiSchemaSource).toContain('pauseRazorpaySubscriptionBodySchema');
    expect(paymentsApiSchemaSource).toContain('resumeRazorpaySubscriptionBodySchema');
    expect(paymentsApiSchemaSource).toContain('createRazorpayItemBodySchema');
    expect(paymentsApiSchemaSource).toContain('createRazorpayPlanBodySchema');
    expect(razorpayConfigRouteSource).toContain('parseZodSchema');
    expect(razorpayConfigRouteSource).toContain('upsertRazorpayConfigBodySchema');
    expect(razorpayRouteSource).toContain('createRazorpayOrderBodySchema');
    expect(razorpayCatalogRouteSource).toContain('createRazorpayItemBodySchema');
    expect(razorpayCatalogRouteSource).toContain('createRazorpayPlanBodySchema');
    expect(razorpayConfigRouteSource).not.toContain("from 'zod'");
    expect(razorpayConfigRouteSource).not.toContain('paymentEnvironmentSchema');
    expect(razorpayConfigRouteSource).not.toContain('function getEnvironment');
  });

  it('keeps environment-scoped config, catalog, and admin reads behind the environment admin guard', () => {
    const adminGuardIndex = stripeRouteSource.indexOf('environmentRouter.use(verifyAdmin)');
    expect(adminGuardIndex).toBeGreaterThan(-1);
    expect(stripeRouteSource.indexOf('environmentRouter.use(stripeConfigRouter)')).toBeGreaterThan(
      adminGuardIndex
    );
    expect(
      stripeRouteSource.indexOf("environmentRouter.use('/catalog', stripeCatalogRouter)")
    ).toBeGreaterThan(adminGuardIndex);
    expect(stripeRouteSource.indexOf("'/transactions'")).toBeGreaterThan(adminGuardIndex);
    expect(stripeRouteSource.indexOf("'/subscriptions'")).toBeGreaterThan(adminGuardIndex);
    expect(stripeRouteSource.indexOf("'/customers'")).toBeGreaterThan(adminGuardIndex);
    expect(stripeRouteSource).toMatch(
      /environmentRouter\.get\(\s*'\/customers'[\s\S]*listPaymentCustomersQuerySchema[\s\S]*listCustomers/
    );
  });

  it('keeps environment-scoped config routes in the dedicated Stripe config router', () => {
    expect(stripeConfigRouteSource).toContain('const router = Router({ mergeParams: true });');
    expect(stripeConfigRouteSource).toMatch(
      /router\.put\(\s*'\/config'[\s\S]*upsertStripeConfigBodySchema/
    );
    expect(stripeConfigRouteSource).toMatch(/router\.delete\(\s*'\/config'/);
    expect(stripeConfigRouteSource).toMatch(/router\.post\(\s*'\/sync'/);
    expect(stripeConfigRouteSource).toMatch(/router\.post\(\s*'\/webhook'/);
    expect(stripeConfigRouteSource).toMatch(/export \{ router as stripeConfigRouter \}/);
    expect(stripeConfigRouteSource).not.toMatch(/router\.get\(\s*'\/status'/);
    expect(stripeConfigRouteSource).not.toMatch(/router\.get\(\s*'\/config'/);
  });

  it('uses the shared payments helper for environment path params', () => {
    expect(paymentHelpersSource).toContain('getPaymentEnvironment');
    expect(paymentHelpersSource).toContain('paymentEnvironmentParamsSchema');
    expect(paymentHelpersSource).toContain('ERROR_CODES.INVALID_INPUT');
    expect(stripeRouteSource).toContain("from '@/services/payments/helpers.js'");
    expect(stripeConfigRouteSource).toContain("from '@/services/payments/helpers.js'");
    expect(stripeCatalogRouteSource).toContain("from '@/services/payments/helpers.js'");
    expect(razorpayRouteSource).toContain("from '@/services/payments/helpers.js'");
    expect(razorpayCatalogRouteSource).toContain("from '@/services/payments/helpers.js'");
    expect(stripeRouteSource).not.toContain('function getEnvironment');
    expect(stripeConfigRouteSource).not.toContain('function getEnvironment');
    expect(stripeCatalogRouteSource).not.toContain('function getEnvironment');
    expect(razorpayRouteSource).not.toContain('function getEnvironment');
    expect(razorpayCatalogRouteSource).not.toContain('function getEnvironment');
    expect(stripeRouteSource).not.toContain("from './route-params.js'");
    expect(stripeConfigRouteSource).not.toContain("from './route-params.js'");
    expect(stripeCatalogRouteSource).not.toContain("from './route-params.js'");
  });

  it('keeps products and prices consolidated in the Stripe catalog router', () => {
    expect(stripeCatalogRouteSource).toMatch(
      /router\.get\(\s*'\/'[\s\S]*productService\.listProducts[\s\S]*priceService\.listPrices/
    );
    expect(stripeCatalogRouteSource).toMatch(/router\.get\(\s*'\/products'/);
    expect(stripeCatalogRouteSource).toMatch(/router\.get\(\s*'\/prices'/);
    expect(stripeCatalogRouteSource).toMatch(
      /router\.post\(\s*'\/products'[\s\S]*createStripeProductBodySchema/
    );
    expect(stripeCatalogRouteSource).toMatch(
      /router\.post\(\s*'\/prices'[\s\S]*createStripePriceBodySchema/
    );
    expect(stripeCatalogRouteSource).toMatch(/export \{ router as stripeCatalogRouter \}/);
    expect(stripeCatalogRouteSource).not.toContain('products.routes');
    expect(stripeCatalogRouteSource).not.toContain('prices.routes');
  });

  it('keeps Razorpay admin webhook setup aligned with the Stripe admin route shape', () => {
    expect(razorpayConfigRouteSource).toContain('const router = Router({ mergeParams: true });');
    expect(razorpayConfigRouteSource).toMatch(/router\.put\(\s*'\/config'/);
    expect(razorpayConfigRouteSource).toMatch(/router\.delete\(\s*'\/config'/);
    expect(razorpayConfigRouteSource).toMatch(/router\.post\(\s*'\/sync'/);
    expect(razorpayConfigRouteSource).toMatch(/router\.get\(\s*'\/webhook'/);
    expect(razorpayConfigRouteSource).toContain('configService.getWebhookSetup(environment)');
    expect(razorpayConfigRouteSource).toMatch(/router\.post\(\s*'\/webhook\/rotate-secret'/);
    expect(razorpayConfigRouteSource).toContain('configService.rotateWebhookSecret(environment)');
    expect(razorpayConfigRouteSource).not.toMatch(/router\.post\(\s*'\/webhook'/);
    expect(razorpayConfigRouteSource).toContain('successResponse(res, result)');
    expect(razorpayRouteSource).not.toContain('webhook-configure');
    expect(razorpayConfigRouteSource).not.toContain('webhook-configure');
    expect(razorpayConfigRouteSource).not.toContain('/webhook-secret');
    expect(razorpayConfigRouteSource).not.toContain('verifyAdmin');
    expect(razorpayRouteSource).not.toContain('x-razorpay-signature');
    expect(razorpayRouteSource).not.toContain('verifyWebhookSignature');
    expect(razorpayConfigRouteSource).toMatch(/export \{ router as razorpayConfigRouter \}/);
  });

  it('keeps Razorpay sync aligned with Stripe provider-level sync shape', () => {
    expect(razorpayRouteSource).toMatch(/router\.post\(\s*'\/sync',\s*verifyAdmin/);
    expect(razorpayConfigRouteSource).toMatch(/router\.post\(\s*'\/sync'/);
    expect(razorpayConfigRouteSource).toContain('syncService.syncAll(environment)');
    expect(razorpayConfigRouteSource).toContain('syncService.syncEnvironmentAfterKeyChange');
    expect(razorpayConfigRouteSource).not.toContain("result.status === 'failed'");
    expect(razorpaySyncServiceSource).toContain('withPaymentSessionAdvisoryLock');
    expect(razorpaySyncServiceSource).toContain('payments_razorpay_environment_');
    expect(razorpaySyncServiceSource).toContain('SyncRazorpayPaymentsResponse');
    expect(razorpaySyncServiceSource).not.toContain('syncPromises');
  });

  it('keeps Razorpay webhooks manual rather than half-managed through a provider API', () => {
    expect(razorpayConfigRouteSource).toContain('getWebhookSetup(environment)');
    expect(razorpayConfigRouteSource).toContain('rotateWebhookSecret(environment)');
    expect(razorpayProviderSource).not.toContain('createWebhook(');
    expect(razorpayProviderSource).not.toContain('/v1/accounts/me/webhooks');
    expect(razorpayConfigServiceSource).toContain('manual');
    expect(razorpayConfigServiceSource).toContain('webhookSecret');
    expect(razorpayConfigServiceSource).not.toContain('manualSetupRequired');
  });

  it('mounts Razorpay inbound webhooks under the dedicated webhook router', () => {
    expect(webhooksRouteSource).toContain("router.use('/stripe', stripeWebhookRouter)");
    expect(webhooksRouteSource).toContain("router.use('/razorpay', razorpayWebhookRouter)");
    expect(razorpayWebhookRouteSource).toMatch(/router\.post\(\s*'\/:environment'/);
    expect(razorpayWebhookRouteSource).toContain('x-razorpay-signature');
    expect(razorpayWebhookRouteSource).toContain('razorpayWebhookParamsSchema');
    expect(razorpayWebhookRouteSource).toContain('handleRazorpayWebhook');
    expect(razorpayWebhookRouteSource).not.toContain('HANDLED_RAZORPAY_EVENTS');
    expect(razorpayWebhookRouteSource).not.toContain('parseRazorpayWebhookPayload');
    expect(razorpayWebhookRouteSource).not.toContain('getRazorpayPayloadEntityId');
    expect(razorpayWebhookRouteSource).not.toContain('isRecord');
    expect(razorpayWebhookRouteSource).not.toContain('RazorpayConfigService');
    expect(razorpayWebhookRouteSource).not.toContain('RazorpaySyncService');
    expect(razorpayWebhookRouteSource).not.toContain('logger');
    expect(razorpayWebhookRouteSource).not.toContain('verifyWebhookSignature');
    expect(razorpayWebhookRouteSource).not.toContain('recordWebhookEventStart');
    expect(razorpayWebhookServiceSource).toContain('HANDLED_RAZORPAY_EVENTS');
    expect(razorpayWebhookServiceSource).toContain('verifyWebhookSignature');
    expect(razorpayWebhookServiceSource).toContain('recordWebhookEventStart');
    expect(razorpayWebhookRouteSource).toMatch(/export \{ router as razorpayWebhookRouter \}/);
  });

  it('accepts only the supported payment environment path params', () => {
    expect(paymentEnvironmentParamsSchema.parse({ environment: 'test' })).toEqual({
      environment: 'test',
    });
    expect(paymentEnvironmentParamsSchema.parse({ environment: 'live' })).toEqual({
      environment: 'live',
    });
    expect(() => paymentEnvironmentParamsSchema.parse({ environment: 'prod' })).toThrow();
  });

  it('accepts Razorpay config and webhook request schemas from shared-schemas', () => {
    expect(razorpayEnvironmentParamsSchema.parse({ environment: 'test' })).toEqual({
      environment: 'test',
    });
    expect(razorpayWebhookParamsSchema.parse({ environment: 'live' })).toEqual({
      environment: 'live',
    });
    expect(() => razorpayEnvironmentParamsSchema.parse({ environment: 'prod' })).toThrow();
    expect(
      upsertRazorpayConfigBodySchema.parse({
        keyId: 'rzp_test_abc',
        keySecret: 'secret',
        webhookSecret: 'webhook_secret',
      })
    ).toEqual({
      keyId: 'rzp_test_abc',
      keySecret: 'secret',
      webhookSecret: 'webhook_secret',
    });
    expect(() =>
      upsertRazorpayConfigBodySchema.parse({ keyId: '', keySecret: 'secret' })
    ).toThrow();
    expect(
      getRazorpayStatusResponseSchema.parse({
        razorpayConnections: [
          {
            environment: 'test',
            status: 'unconfigured',
            accountId: null,
            merchantName: null,
            accountLivemode: null,
            webhookEndpointId: null,
            webhookEndpointUrl: null,
            webhookConfiguredAt: null,
            maskedKey: null,
            lastSyncedAt: null,
            lastSyncStatus: null,
            lastSyncError: null,
            lastSyncCounts: {},
          },
        ],
      })
    ).toEqual(
      expect.objectContaining({
        razorpayConnections: expect.any(Array),
      })
    );
    expect(
      getRazorpayConfigResponseSchema.parse({
        keys: [
          {
            environment: 'test',
            keyType: 'api_key',
            value: 'rzp_test_key1234',
          },
        ],
      })
    ).toEqual(
      expect.objectContaining({
        keys: expect.any(Array),
      })
    );
    expect(
      getRazorpayWebhookSetupResponseSchema.parse({
        connection: {
          environment: 'test',
          status: 'connected',
          accountId: 'acc_123',
          merchantName: 'Example Merchant',
          accountLivemode: false,
          webhookEndpointId: 'manual',
          webhookEndpointUrl: 'https://api.example.com/api/webhooks/razorpay/test',
          webhookConfiguredAt: null,
          maskedKey: 'rzp_test_****1234',
          lastSyncedAt: null,
          lastSyncStatus: null,
          lastSyncError: null,
          lastSyncCounts: {},
        },
        webhookUrl: 'https://api.example.com/api/webhooks/razorpay/test',
        webhookSecret: 'manual_secret',
      })
    ).toEqual(
      expect.objectContaining({
        webhookUrl: 'https://api.example.com/api/webhooks/razorpay/test',
        webhookSecret: 'manual_secret',
      })
    );
    expect(
      syncRazorpayPaymentsResponseSchema.parse({
        results: [
          {
            environment: 'test',
            status: 'failed',
            connection: {
              environment: 'test',
              status: 'unconfigured',
              accountId: null,
              merchantName: null,
              accountLivemode: null,
              webhookEndpointId: null,
              webhookEndpointUrl: null,
              webhookConfiguredAt: null,
              maskedKey: null,
              lastSyncedAt: null,
              lastSyncStatus: 'failed',
              lastSyncError: 'missing keys',
              lastSyncCounts: {},
            },
            syncCounts: {
              plans: 0,
              items: 0,
              customers: 0,
              subscriptions: 0,
              invoices: 0,
              payments: 0,
            },
            error: 'missing keys',
          },
        ],
      })
    ).toEqual(
      expect.objectContaining({
        results: expect.any(Array),
      })
    );
  });

  it('accepts empty catalog and products query strings for environment-scoped reads', () => {
    expect(listStripeCatalogQuerySchema.parse({})).toEqual({});
    expect(listStripeProductsQuerySchema.parse({})).toEqual({});
    expect(() => listStripeCatalogQuerySchema.parse({ environment: 'test' })).toThrow();
  });

  it('accepts Stripe key configuration bodies without embedding environment in the body', () => {
    expect(
      upsertStripeConfigBodySchema.parse({
        secretKey: FAKE_LIVE_SECRET_KEY,
      })
    ).toEqual({
      secretKey: FAKE_LIVE_SECRET_KEY,
    });
    expect(() => upsertStripeConfigBodySchema.parse({ secretKey: '' })).toThrow();
    expect(() =>
      upsertStripeConfigBodySchema.parse({
        environment: 'live',
        secretKey: FAKE_LIVE_SECRET_KEY,
      })
    ).toThrow();
  });

  it('accepts product CRUD bodies without embedding environment in the body', () => {
    expect(
      createStripeProductBodySchema.parse({
        name: 'Pro',
        description: null,
        active: true,
        metadata: { tier: 'pro' },
        idempotencyKey: 'agent-product-123',
      })
    ).toEqual({
      name: 'Pro',
      description: null,
      active: true,
      metadata: { tier: 'pro' },
      idempotencyKey: 'agent-product-123',
    });

    expect(() =>
      createStripeProductBodySchema.parse({ name: 'Pro', environment: 'test' })
    ).toThrow();
    expect(() =>
      createStripeProductBodySchema.parse({
        name: 'Pro',
        idempotencyKey: 'x'.repeat(201),
      })
    ).toThrow(/200 characters/i);
    expect(() => updateStripeProductBodySchema.parse({})).toThrow();
    expect(updateStripeProductBodySchema.parse({ active: false })).toEqual({
      active: false,
    });
    expect(() => updateStripeProductBodySchema.parse({ environment: 'live' })).toThrow();
  });

  it('accepts price CRUD bodies and query filters without embedding environment in the body', () => {
    expect(listStripePricesQuerySchema.parse({ productId: 'prod_123' })).toEqual({
      productId: 'prod_123',
    });
    expect(
      createStripePriceBodySchema.parse({
        productId: 'prod_123',
        currency: 'USD',
        unitAmount: 2000,
        recurring: { interval: 'month', intervalCount: 1 },
        idempotencyKey: 'agent-price-123',
      })
    ).toEqual({
      productId: 'prod_123',
      currency: 'usd',
      unitAmount: 2000,
      recurring: { interval: 'month', intervalCount: 1 },
      idempotencyKey: 'agent-price-123',
    });
    expect(() =>
      createStripePriceBodySchema.parse({
        productId: 'prod_123',
        currency: 'usd',
        unitAmount: 2000,
        environment: 'test',
      })
    ).toThrow();
    expect(() => updateStripePriceBodySchema.parse({})).toThrow();
    expect(updateStripePriceBodySchema.parse({ active: false })).toEqual({ active: false });
    expect(() => updateStripePriceBodySchema.parse({ environment: 'live' })).toThrow();
  });

  it('accepts Razorpay native order and subscription checkout bodies', () => {
    expect(
      createRazorpayOrderBodySchema.parse({
        amount: 50000,
        currency: 'inr',
        receipt: 'order_123',
        subject: { type: 'team', id: 'team_123' },
        customerEmail: 'buyer@example.com',
      })
    ).toEqual({
      amount: 50000,
      currency: 'INR',
      receipt: 'order_123',
      subject: { type: 'team', id: 'team_123' },
      customerEmail: 'buyer@example.com',
    });

    expect(
      verifyRazorpayOrderBodySchema.parse({
        orderId: 'order_123',
        paymentId: 'pay_123',
        signature: 'signature',
      })
    ).toEqual({
      orderId: 'order_123',
      paymentId: 'pay_123',
      signature: 'signature',
    });

    expect(
      createRazorpaySubscriptionBodySchema.parse({
        planId: 'plan_123',
        totalCount: 12,
        subject: { type: 'team', id: 'team_123' },
        customerContact: '+919999999999',
      })
    ).toEqual({
      planId: 'plan_123',
      totalCount: 12,
      subject: { type: 'team', id: 'team_123' },
      customerContact: '+919999999999',
    });

    expect(() =>
      createRazorpaySubscriptionBodySchema.parse({
        planId: 'plan_123',
        subject: { type: 'team', id: 'team_123' },
      })
    ).toThrow(/totalCount or endAt/i);

    expect(
      verifyRazorpaySubscriptionBodySchema.parse({
        subscriptionId: 'sub_123',
        paymentId: 'pay_123',
        signature: 'signature',
      })
    ).toEqual({
      subscriptionId: 'sub_123',
      paymentId: 'pay_123',
      signature: 'signature',
    });

    expect(cancelRazorpaySubscriptionBodySchema.parse({})).toEqual({
      cancelAtCycleEnd: false,
    });
    expect(cancelRazorpaySubscriptionBodySchema.parse({ cancelAtCycleEnd: true })).toEqual({
      cancelAtCycleEnd: true,
    });
    expect(pauseRazorpaySubscriptionBodySchema.parse({})).toEqual({});
    expect(resumeRazorpaySubscriptionBodySchema.parse({})).toEqual({});
    expect(() => pauseRazorpaySubscriptionBodySchema.parse({ planId: 'plan_456' })).toThrow();
    expect(
      razorpaySubscriptionParamsSchema.parse({
        environment: 'test',
        subscriptionId: 'sub_123',
      })
    ).toEqual({
      environment: 'test',
      subscriptionId: 'sub_123',
    });
  });

  it('accepts Razorpay item and plan mutation bodies without pretending plans are editable', () => {
    expect(
      createRazorpayItemBodySchema.parse({
        name: 'Invoice item',
        amount: 25000,
        currency: 'inr',
      })
    ).toEqual({
      name: 'Invoice item',
      amount: 25000,
      currency: 'INR',
    });

    expect(updateRazorpayItemBodySchema.parse({ active: false })).toEqual({ active: false });
    expect(() => updateRazorpayItemBodySchema.parse({})).toThrow();

    expect(
      createRazorpayPlanBodySchema.parse({
        period: 'monthly',
        interval: 1,
        item: {
          name: 'Pro monthly',
          amount: 199900,
          currency: 'inr',
        },
        notes: { tier: 'pro' },
      })
    ).toEqual({
      period: 'monthly',
      interval: 1,
      item: {
        name: 'Pro monthly',
        amount: 199900,
        currency: 'INR',
      },
      notes: { tier: 'pro' },
    });

    expect(razorpayCatalogRouteSource).not.toMatch(/router\.patch\(\s*'\/plans/);
    expect(razorpayCatalogRouteSource).not.toMatch(/router\.delete\(\s*'\/plans/);
  });

  it('rejects caller-provided InsForge-reserved Razorpay notes', () => {
    expect(() =>
      createRazorpayOrderBodySchema.parse({
        amount: 50000,
        currency: 'INR',
        notes: {
          insforge_subject_type: 'team',
        },
      })
    ).toThrow(/reserved/i);

    expect(() =>
      createRazorpayPlanBodySchema.parse({
        period: 'monthly',
        interval: 1,
        item: {
          name: 'Pro monthly',
          amount: 199900,
          currency: 'INR',
        },
        notes: {
          insforge_subject_id: 'team_123',
        },
      })
    ).toThrow(/reserved/i);
  });

  it('allows anonymous one-time checkout sessions without embedding environment in the body', () => {
    expect(
      createCheckoutSessionBodySchema.parse({
        mode: 'payment',
        lineItems: [{ priceId: 'price_123', quantity: 2 }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        customerEmail: 'buyer@example.com',
        idempotencyKey: 'checkout-123',
      })
    ).toEqual({
      mode: 'payment',
      lineItems: [{ priceId: 'price_123', quantity: 2 }],
      successUrl: 'https://example.com/success',
      cancelUrl: 'https://example.com/cancel',
      customerEmail: 'buyer@example.com',
      idempotencyKey: 'checkout-123',
    });
  });

  it('rejects caller-provided InsForge-reserved checkout metadata', () => {
    expect(() =>
      createCheckoutSessionBodySchema.parse({
        mode: 'payment',
        lineItems: [{ priceId: 'price_123' }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        metadata: {
          insforge_subject_type: 'team',
          insforge_subject_id: 'team_victim',
        },
      })
    ).toThrow(/reserved/i);
  });

  it('requires subscription checkout sessions to specify a billing subject', () => {
    expect(() =>
      createCheckoutSessionBodySchema.parse({
        mode: 'subscription',
        lineItems: [{ priceId: 'price_123' }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
      })
    ).toThrow(/billing subject/i);

    expect(
      createCheckoutSessionBodySchema.parse({
        mode: 'subscription',
        lineItems: [{ priceId: 'price_123' }],
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        subject: { type: 'team', id: 'team_123' },
      })
    ).toEqual({
      mode: 'subscription',
      lineItems: [{ priceId: 'price_123', quantity: 1 }],
      successUrl: 'https://example.com/success',
      cancelUrl: 'https://example.com/cancel',
      subject: { type: 'team', id: 'team_123' },
    });
  });

  it('requires customer portal session bodies to specify a billing subject without embedding environment', () => {
    expect(
      createCustomerPortalSessionBodySchema.parse({
        subject: { type: 'team', id: 'team_123' },
        returnUrl: 'https://example.com/account',
        configuration: 'bpc_123',
      })
    ).toEqual({
      subject: { type: 'team', id: 'team_123' },
      returnUrl: 'https://example.com/account',
      configuration: 'bpc_123',
    });

    expect(() =>
      createCustomerPortalSessionBodySchema.parse({
        returnUrl: 'https://example.com/account',
      })
    ).toThrow();
    expect(() =>
      createCustomerPortalSessionBodySchema.parse({
        subject: { type: 'team', id: 'team_123' },
        returnUrl: 'not-a-url',
      })
    ).toThrow(/valid URL/i);
    expect(() =>
      createCustomerPortalSessionBodySchema.parse({
        environment: 'test',
        subject: { type: 'team', id: 'team_123' },
      })
    ).toThrow();
  });

  it('requires runtime list query filters to omit environment and keep complete subject filters', () => {
    expect(listPaymentTransactionsQuerySchema.parse({})).toEqual({ limit: 50 });
    expect(
      listStripeSubscriptionsQuerySchema.parse({
        subjectType: 'organization',
        subjectId: 'org_123',
        limit: '25',
      })
    ).toEqual({
      subjectType: 'organization',
      subjectId: 'org_123',
      limit: 25,
    });
    expect(
      listRazorpaySubscriptionsQuerySchema.parse({
        subjectType: 'organization',
        subjectId: 'org_123',
        limit: '25',
      })
    ).toEqual({
      subjectType: 'organization',
      subjectId: 'org_123',
      limit: 25,
    });

    expect(() => listPaymentTransactionsQuerySchema.parse({ environment: 'live' })).toThrow();
    expect(() => listStripeSubscriptionsQuerySchema.parse({ subjectType: 'team' })).toThrow(
      /provided together/i
    );
    expect(() => listRazorpaySubscriptionsQuerySchema.parse({ subjectType: 'team' })).toThrow(
      /provided together/i
    );
  });

  it('requires admin customer mirror reads to omit environment from the query and normalize limit', () => {
    expect(
      listPaymentCustomersQuerySchema.parse({
        limit: '25',
      })
    ).toEqual({
      limit: 25,
    });

    expect(listPaymentCustomersQuerySchema.parse({})).toEqual({
      limit: 50,
    });

    expect(() => listPaymentCustomersQuerySchema.parse({ environment: 'test' })).toThrow();
    expect(() => listPaymentCustomersQuerySchema.parse({ limit: 0 })).toThrow();
  });
});
