/**
 * Tests for GET/POST /api/promos route.
 *
 * Covers:
 * GET:
 * - 401 when not authenticated
 * - 200 lists promos with mapped fields
 * - 200 filters by active param
 * - Handles coupon as expanded object vs string ID
 * - 500 when Stripe throws
 *
 * POST:
 * - 403 for non-admin
 * - 400 when code is missing
 * - 400 when neither percentOff nor amountOff provided
 * - 201 creates coupon + promo with percentOff
 * - 201 creates coupon + promo with amountOff
 * - 201 creates with maxRedemptions and expiresAt
 * - 500 when Stripe throws (surfaces error message)
 */

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

// ── Mocks ──────────────────────────────────────────────────────────────

const mockGetCurrentUser = vi.fn();
vi.mock('@/lib/auth-server', () => ({
  getCurrentUser: (...args: unknown[]) => mockGetCurrentUser(...args),
  getCurrentUserFromRequest: (...args: unknown[]) => mockGetCurrentUser(...args),
  requireAdmin: (...args: unknown[]) => mockGetCurrentUser(...args),
}));

const mockListPromos = vi.fn();
const mockCreatePromo = vi.fn();

vi.mock('@/lib/growth-client', () => ({
  listPromos: (...args: unknown[]) => mockListPromos(...args),
  createPromo: (...args: unknown[]) => mockCreatePromo(...args),
}));

import { GET, POST } from '@/app/api/promos/route';

// ── Helpers ────────────────────────────────────────────────────────────

const adminUser = {
  id: 'usr_admin_1',
  email: 'admin@example.com',
  name: 'Admin',
  role: 'super_admin',
  plan: 'enterprise',
};

const viewerUser = {
  id: 'usr_viewer_1',
  email: 'viewer@example.com',
  name: 'Viewer',
  role: 'viewer',
  plan: 'free',
};

async function callGET(params = '', token = 'Bearer admin_tok') {
  const { NextRequest } = await import('next/server');
  return GET(
    new NextRequest(
      new Request(`http://localhost:3001/api/promos${params}`, {
        headers: { Authorization: token },
      })
    )
  );
}

async function callPOST(body: object, token = 'Bearer admin_tok') {
  const { NextRequest } = await import('next/server');
  return POST(
    new NextRequest(
      new Request('http://localhost:3001/api/promos', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: token,
        },
        body: JSON.stringify(body),
      })
    )
  );
}

// Sample Stripe promo code with expanded coupon object (Stripe SDK v20+: coupon under promotion)
const samplePromo = {
  id: 'promo_abc123',
  code: 'LAUNCH20',
  active: true,
  promotion: {
    coupon: {
      id: 'coup_xyz',
      percent_off: 20,
      amount_off: null,
      currency: null,
      duration: 'once',
    },
    type: 'coupon',
  },
  times_redeemed: 3,
  max_redemptions: 100,
  expires_at: 1767225600, // 2026-01-01T00:00:00Z
  created: 1736689200, // 2025-01-12
  metadata: { createdBy: 'usr_admin_1' },
};

// Promo with coupon as string ID (not expanded)
const _samplePromoStringCoupon = {
  id: 'promo_def456',
  code: 'VIP50',
  active: false,
  promotion: {
    coupon: 'coup_string_id',
    type: 'coupon',
  },
  times_redeemed: 0,
  max_redemptions: null,
  expires_at: null,
  created: 1736689200,
  metadata: {},
};

// ── Tests ──────────────────────────────────────────────────────────────

describe('GET /api/promos', () => {
  beforeEach(() => vi.clearAllMocks());

  it('returns 401 when not authenticated', async () => {
    mockGetCurrentUser.mockResolvedValue(null);
    const res = await callGET();
    expect(res.status).toBe(401);
  });

  it('lists promos via growth service', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockListPromos.mockResolvedValue({
      promos: [samplePromo],
    });

    const res = await callGET();
    expect(res.status).toBe(200);
    const data = await res.json();
    expect(data.promos).toHaveLength(1);
    expect(data.promos[0].code).toBe('LAUNCH20');
  });

  it('passes active filter to growth service', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockListPromos.mockResolvedValue({ promos: [] });

    await callGET('?active=true');
    expect(mockListPromos).toHaveBeenCalledWith(true);
  });

  it('passes undefined when no active filter', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockListPromos.mockResolvedValue({ promos: [] });

    await callGET();
    expect(mockListPromos).toHaveBeenCalledWith(undefined);
  });

  it('returns 500 when growth service throws', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockListPromos.mockRejectedValue(new Error('Growth Service error'));

    const res = await callGET();
    expect(res.status).toBe(500);
  });
});

describe('POST /api/promos', () => {
  beforeEach(() => vi.clearAllMocks());

  it('returns 403 for non-admin', async () => {
    mockGetCurrentUser.mockResolvedValue(viewerUser);
    const res = await callPOST({ code: 'TEST', percentOff: 10 });
    expect(res.status).toBe(403);
  });

  it('returns 400 when code is missing', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    const res = await callPOST({ percentOff: 10 });
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain('code');
  });

  it('returns 400 when neither percentOff nor amountOff', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    const res = await callPOST({ code: 'NODISC' });
    expect(res.status).toBe(400);
    const data = await res.json();
    expect(data.error).toContain('percentOff');
  });

  it('creates promo with percentOff via growth service', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockCreatePromo.mockResolvedValue({
      id: 'promo_new',
      code: 'SUMMER25',
      percentOff: 25,
      couponId: 'coup_new',
    });

    const res = await callPOST({ code: 'summer25', percentOff: 25 });
    expect(res.status).toBe(201);
    const data = await res.json();
    expect(data.code).toBe('SUMMER25');
    expect(data.percentOff).toBe(25);
    expect(data.couponId).toBe('coup_new');

    expect(mockCreatePromo).toHaveBeenCalledWith(
      expect.objectContaining({
        code: 'summer25',
        percentOff: 25,
        createdBy: 'usr_admin_1',
      })
    );
  });

  it('creates promo with amountOff via growth service', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockCreatePromo.mockResolvedValue({
      id: 'promo_amt',
      code: 'SAVE5',
      amountOff: 500,
    });

    const res = await callPOST({ code: 'SAVE5', amountOff: 500, currency: 'usd' });
    expect(res.status).toBe(201);

    expect(mockCreatePromo).toHaveBeenCalledWith(
      expect.objectContaining({
        code: 'SAVE5',
        amountOff: 500,
        currency: 'usd',
      })
    );
  });

  it('passes maxRedemptions and expiresAt', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockCreatePromo.mockResolvedValue({
      id: 'promo_lim',
      code: 'LIMITED',
      maxRedemptions: 50,
      expiresAt: '2026-01-01T00:00:00Z',
    });

    const res = await callPOST({
      code: 'LIMITED',
      percentOff: 10,
      duration: 'forever',
      maxRedemptions: 50,
      expiresAt: '2026-01-01T00:00:00Z',
    });
    expect(res.status).toBe(201);
    const data = await res.json();
    expect(data.maxRedemptions).toBe(50);
    expect(data.expiresAt).toBeTruthy();

    expect(mockCreatePromo).toHaveBeenCalledWith(
      expect.objectContaining({
        maxRedemptions: 50,
        expiresAt: '2026-01-01T00:00:00Z',
      })
    );
  });

  it('returns 500 with error message when growth service throws', async () => {
    mockGetCurrentUser.mockResolvedValue(adminUser);
    mockCreatePromo.mockRejectedValue(new Error('Invalid coupon params'));

    const res = await callPOST({ code: 'BAD', percentOff: -5 });
    expect(res.status).toBe(500);
    const data = await res.json();
    expect(data.error).toBe('Invalid coupon params');
  });
});
