/**
 * Route-level tests for promos module — Fastify inject with mocked Stripe.
 */

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

const stripeMock = {
  promotionCodes: {
    list: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
  },
  coupons: {
    create: vi.fn(),
  },
};

vi.mock('stripe', () => ({
  default: vi.fn().mockImplementation(() => stripeMock),
}));

vi.mock('../../lib/request-context.js', () => ({
  getRequestProductId: () => 'lysnrai',
}));

function buildPromo(overrides?: Partial<Record<string, unknown>>) {
  return {
    id: 'promo_1',
    code: 'SAVE20',
    active: true,
    promotion: {
      type: 'coupon',
      coupon: {
        id: 'coupon_1',
        percent_off: 20,
        amount_off: null,
        currency: 'usd',
        duration: 'once',
      },
    },
    times_redeemed: 0,
    max_redemptions: null,
    expires_at: null,
    created: 1708000000,
    metadata: { createdBy: 'admin', productId: 'lysnrai' },
    ...overrides,
  };
}

async function buildApp() {
  const { promoRoutes } = await import('./routes.js');
  const app = Fastify({ logger: false });
  await app.register(promoRoutes, { prefix: '/api' });
  return app;
}

describe('promoRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    process.env.STRIPE_SECRET_KEY = 'sk_test_123';
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('GET /promos lists promo codes', async () => {
    stripeMock.promotionCodes.list.mockResolvedValue({ data: [buildPromo()] });
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/promos?active=true' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.promos).toHaveLength(1);
    expect(data.promos[0].code).toBe('SAVE20');
  });

  it('POST /promos returns 400 when discount fields are missing', async () => {
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/promos',
      payload: { code: 'bad', createdBy: 'admin' },
    });

    expect(res.statusCode).toBe(400);
  });

  it('POST /promos creates coupon and promotion code', async () => {
    stripeMock.coupons.create.mockResolvedValue({ id: 'coupon_1' });
    stripeMock.promotionCodes.create.mockResolvedValue(buildPromo());
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/promos',
      payload: {
        code: 'save-20!',
        percentOff: 20,
        duration: 'once',
        createdBy: 'admin',
      },
    });

    expect(res.statusCode).toBe(201);
    const data = JSON.parse(res.body);
    expect(data.code).toBe('SAVE20');
    expect(stripeMock.promotionCodes.create).toHaveBeenCalled();
  });

  it('POST /promos/validate returns 400 without code', async () => {
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/promos/validate',
      payload: {},
    });

    expect(res.statusCode).toBe(400);
  });

  it('POST /promos/validate returns invalid when no active promo', async () => {
    stripeMock.promotionCodes.list.mockResolvedValue({ data: [] });
    const app = await buildApp();

    const res = await app.inject({
      method: 'POST',
      url: '/api/promos/validate',
      payload: { code: 'NOPE' },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.valid).toBe(false);
    expect(data.promo).toBeNull();
  });

  it('PUT /promos/:id/deactivate deactivates promo', async () => {
    stripeMock.promotionCodes.update.mockResolvedValue(buildPromo({ active: false }));
    const app = await buildApp();

    const res = await app.inject({ method: 'PUT', url: '/api/promos/promo_1/deactivate' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.active).toBe(false);
  });

  it('PUT /promos/:id/deactivate returns 400 on Stripe error', async () => {
    stripeMock.promotionCodes.update.mockRejectedValue(new Error('stripe failure'));
    const app = await buildApp();

    const res = await app.inject({ method: 'PUT', url: '/api/promos/promo_bad/deactivate' });

    expect(res.statusCode).toBe(400);
  });
});
