/**
 * Route-level tests for stripe webhook migration behavior.
 */

import Fastify from 'fastify';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const mockStripeClient = {
  webhooks: {
    constructEvent: vi.fn(),
  },
};

const stripeLibMock = {
  getStripeForProduct: vi.fn(),
  getPriceIds: vi.fn(),
};

const subRepoMock = {
  getByUserId: vi.fn(),
  createSubscription: vi.fn(),
  updateSubscription: vi.fn(),
  createPayment: vi.fn(),
  getByStripeCustomerId: vi.fn(),
  getByStripeCustomerIdAnyProduct: vi.fn(),
};

const authRepoMock = {
  updatePlan: vi.fn(),
};

const requestContextMock = {
  getRequestProductId: vi.fn(),
};

vi.mock('../../lib/stripe.js', () => stripeLibMock);
vi.mock('../subscriptions/repository.js', () => subRepoMock);
vi.mock('../auth/repository.js', () => authRepoMock);
vi.mock('../../lib/request-context.js', () => requestContextMock);

describe('stripeRoutes webhook', () => {
  beforeEach(() => {
    vi.clearAllMocks();

    process.env.STRIPE_WEBHOOK_SECRET = 'whsec_test';

    stripeLibMock.getStripeForProduct.mockReturnValue(mockStripeClient);
    stripeLibMock.getPriceIds.mockReturnValue({
      pro: 'price_pro',
      enterprise: 'price_enterprise',
    });

    requestContextMock.getRequestProductId.mockReturnValue('lysnrai');

    subRepoMock.getByUserId.mockResolvedValue(null);
    subRepoMock.createSubscription.mockResolvedValue(undefined);
    subRepoMock.updateSubscription.mockResolvedValue(undefined);
    subRepoMock.createPayment.mockResolvedValue(undefined);
    subRepoMock.getByStripeCustomerId.mockResolvedValue(null);
    subRepoMock.getByStripeCustomerIdAnyProduct.mockResolvedValue(null);

    authRepoMock.updatePlan.mockResolvedValue(undefined);

    vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true }));
  });

  it('falls back to any-product lookup when product metadata is missing (subscription.updated)', async () => {
    mockStripeClient.webhooks.constructEvent.mockReturnValue({
      type: 'customer.subscription.updated',
      data: {
        object: {
          customer: 'cus_123',
          cancel_at_period_end: false,
          status: 'active',
          current_period_end: 1_900_000_000,
          items: { data: [{ price: { id: 'price_enterprise' } }] },
        },
      },
    });

    subRepoMock.getByStripeCustomerIdAnyProduct.mockResolvedValue({
      id: 'sub_1',
      productId: 'mindlyst',
      userId: 'usr_1',
      plan: 'pro',
      status: 'active',
    });

    const { stripeRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(stripeRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/stripe/webhook',
      headers: {
        'stripe-signature': 'sig_test',
        'content-type': 'application/json',
      },
      payload: '{"id":"evt_1"}',
    });

    expect(res.statusCode).toBe(200);
    expect(subRepoMock.getByStripeCustomerIdAnyProduct).toHaveBeenCalledWith('cus_123');
    expect(subRepoMock.updateSubscription).toHaveBeenCalledTimes(1);
    expect(subRepoMock.updateSubscription.mock.calls[0][2]).toMatchObject({
      plan: 'enterprise',
    });
    expect(authRepoMock.updatePlan).toHaveBeenCalledWith('usr_1', 'mindlyst', 'enterprise');

    await app.close();
  });

  it('normalizes invalid checkout metadata plan to pro', async () => {
    mockStripeClient.webhooks.constructEvent.mockReturnValue({
      type: 'checkout.session.completed',
      data: {
        object: {
          metadata: {
            userId: 'usr_2',
            productId: 'lysnrai',
            plan: 'premium',
          },
          customer: 'cus_777',
          subscription: 'sub_777',
          amount_total: 0,
          currency: 'usd',
        },
      },
    });

    const { stripeRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(stripeRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/stripe/webhook',
      headers: {
        'stripe-signature': 'sig_test',
        'content-type': 'application/json',
      },
      payload: '{"id":"evt_2"}',
    });

    expect(res.statusCode).toBe(200);
    expect(subRepoMock.createSubscription).toHaveBeenCalledTimes(1);
    expect(subRepoMock.createSubscription.mock.calls[0][0]).toMatchObject({
      plan: 'pro',
      productId: 'lysnrai',
      userId: 'usr_2',
    });
    expect(authRepoMock.updatePlan).toHaveBeenCalledWith('usr_2', 'lysnrai', 'pro');

    await app.close();
  });
});
