import { describe, it, expect, vi, beforeEach } from 'vitest';
import { createBillingClient, BillingApiError } from './index.js';
import type { BillingClient } from './index.js';

function mockFetch(status: number, body: unknown) {
  return vi.fn().mockResolvedValue({
    ok: status >= 200 && status < 300,
    status,
    json: () => Promise.resolve(body),
  });
}

describe('createBillingClient', () => {
  let client: BillingClient;
  const config = {
    baseUrl: 'http://localhost:4003/api',
    productId: 'notelett',
    getAccessToken: () => 'test-token',
  };

  beforeEach(() => {
    client = createBillingClient(config);
  });

  it('listPlans — returns plans array', async () => {
    const plans = [{ name: 'free', displayName: 'Free', price: 0 }];
    globalThis.fetch = mockFetch(200, { plans });
    const result = await client.listPlans();
    expect(result).toEqual(plans);
    expect(globalThis.fetch).toHaveBeenCalledWith(
      'http://localhost:4003/api/plans',
      expect.objectContaining({ method: 'GET' })
    );
  });

  it('getPlan — returns single plan', async () => {
    const plan = { name: 'pro', displayName: 'Pro', price: 9.99 };
    globalThis.fetch = mockFetch(200, plan);
    const result = await client.getPlan('pro');
    expect(result).toEqual(plan);
  });

  it('getSubscription — returns subscription', async () => {
    const sub = { id: 'sub_1', plan: 'free', status: 'active' };
    globalThis.fetch = mockFetch(200, sub);
    const result = await client.getSubscription();
    expect(result).toEqual(sub);
  });

  it('getSubscription — returns null on 404', async () => {
    globalThis.fetch = mockFetch(404, { message: 'Not found' });
    const result = await client.getSubscription();
    expect(result).toBeNull();
  });

  it('changePlan — sends plan in body', async () => {
    const sub = { id: 'sub_1', plan: 'pro', status: 'active' };
    globalThis.fetch = mockFetch(200, sub);
    const result = await client.changePlan('pro');
    expect(result.plan).toBe('pro');
    expect(globalThis.fetch).toHaveBeenCalledWith(
      'http://localhost:4003/api/subscriptions/me/change-plan',
      expect.objectContaining({
        method: 'POST',
        body: JSON.stringify({ plan: 'pro' }),
      })
    );
  });

  it('cancelSubscription — POST to cancel endpoint', async () => {
    const sub = { id: 'sub_1', plan: 'pro', cancelAtPeriodEnd: true };
    globalThis.fetch = mockFetch(200, sub);
    const result = await client.cancelSubscription();
    expect(result.cancelAtPeriodEnd).toBe(true);
  });

  it('resumeSubscription — POST to resume endpoint', async () => {
    const sub = { id: 'sub_1', plan: 'pro', cancelAtPeriodEnd: false };
    globalThis.fetch = mockFetch(200, sub);
    const result = await client.resumeSubscription();
    expect(result.cancelAtPeriodEnd).toBe(false);
  });

  it('listPayments — returns payments array', async () => {
    const payments = [{ id: 'pay_1', amount: 999, status: 'succeeded' }];
    globalThis.fetch = mockFetch(200, { payments });
    const result = await client.listPayments();
    expect(result).toEqual(payments);
  });

  it('getUsage — returns usage summary', async () => {
    const usage = { tokensUsed: 500, tokensIncluded: 10000, tokensRemaining: 9500, percentUsed: 5 };
    globalThis.fetch = mockFetch(200, usage);
    const result = await client.getUsage();
    expect(result.tokensRemaining).toBe(9500);
  });

  it('sends auth header and product-id header', async () => {
    globalThis.fetch = mockFetch(200, { plans: [] });
    await client.listPlans();
    const call = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0];
    const headers = call[1].headers;
    expect(headers['Authorization']).toBe('Bearer test-token');
    expect(headers['x-product-id']).toBe('notelett');
  });

  it('throws BillingApiError on non-ok response', async () => {
    globalThis.fetch = mockFetch(403, { message: 'Forbidden' });
    await expect(client.changePlan('enterprise')).rejects.toThrow(BillingApiError);
  });

  it('works without access token', async () => {
    const noAuthClient = createBillingClient({ ...config, getAccessToken: () => null });
    globalThis.fetch = mockFetch(200, { plans: [] });
    await noAuthClient.listPlans();
    const headers = (globalThis.fetch as ReturnType<typeof vi.fn>).mock.calls[0][1].headers;
    expect(headers['Authorization']).toBeUndefined();
  });
});
