import { describe, it, expect, vi, afterEach } from 'vitest';
import { createReferralClient } from './client.js';
import type { ReferralDoc } from './types.js';

const baseConfig = {
  baseUrl: 'http://localhost:4003/api',
  productId: 'testapp',
  getAccessToken: () => 'test-token',
};

function mockReferral(overrides?: Partial<ReferralDoc>): ReferralDoc {
  return {
    id: 'ref-1',
    productId: 'testapp',
    referrerId: 'user-1',
    referrerEmail: 'alice@test.com',
    referredUserId: null,
    referredEmail: 'bob@test.com',
    status: 'pending',
    referrerRewardTokens: 1000,
    referredRewardTokens: 500,
    referrerRewarded: false,
    referredRewarded: false,
    createdAt: '2026-01-01T00:00:00Z',
    completedAt: null,
    ...overrides,
  };
}

describe('createReferralClient', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should create a referral', async () => {
    const ref = mockReferral();
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(ref),
      })
    );

    const client = createReferralClient(baseConfig);
    const result = await client.createReferral({
      referrerId: 'user-1',
      referrerEmail: 'alice@test.com',
      referredEmail: 'bob@test.com',
    });

    expect(result).toEqual(ref);
    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/referrals',
      expect.objectContaining({ method: 'POST' })
    );
  });

  it('should list referrals by referrerId', async () => {
    const data = { referrals: [mockReferral()], count: 1 };
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(data),
      })
    );

    const client = createReferralClient(baseConfig);
    const result = await client.listMyReferrals('user-1');

    expect(result.count).toBe(1);
    expect(result.referrals).toHaveLength(1);
    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/referrals/by-referrer/user-1',
      expect.any(Object)
    );
  });

  it('should get referral stats', async () => {
    const stats = { total: 10, completed: 5, rewarded: 3 };
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(stats),
      })
    );

    const client = createReferralClient(baseConfig);
    const result = await client.getReferralStats();
    expect(result).toEqual(stats);
  });

  it('should update referral status via PUT', async () => {
    const updated = mockReferral({ status: 'signed_up' });
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(updated),
      })
    );

    const client = createReferralClient(baseConfig);
    const result = await client.updateReferralStatus('ref-1', 'user-1', 'signed_up');

    expect(result.status).toBe('signed_up');
    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/referrals/ref-1',
      expect.objectContaining({ method: 'PUT' })
    );
  });

  it('should get referral by email', async () => {
    const ref = mockReferral();
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(ref),
      })
    );

    const client = createReferralClient(baseConfig);
    const result = await client.getByEmail('bob@test.com');
    expect(result).toEqual(ref);
  });

  it('should return null for 404 on getByEmail', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: false,
        status: 404,
      })
    );

    const client = createReferralClient(baseConfig);
    const result = await client.getByEmail('unknown@test.com');
    expect(result).toBeNull();
  });

  it('should send correct headers', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({ total: 0, completed: 0, rewarded: 0 }),
      })
    );

    const client = createReferralClient(baseConfig);
    await client.getReferralStats();

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    const callHeaders = fetchMock.mock.calls[0][1].headers as Record<string, string>;
    expect(callHeaders['x-product-id']).toBe('testapp');
    expect(callHeaders['Authorization']).toBe('Bearer test-token');
    expect(callHeaders['x-request-id']).toBeDefined();
  });

  it('should throw on non-ok response', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: false,
        status: 500,
      })
    );

    const client = createReferralClient(baseConfig);
    await expect(client.getReferralStats()).rejects.toThrow('getReferralStats failed: 500');
  });

  it('should build share link', () => {
    const client = createReferralClient(baseConfig);
    const link = client.buildShareLink('ABC123');
    expect(link).toContain('refer/ABC123');
    expect(link).toContain('product=testapp');
  });

  it('should build share message', () => {
    const client = createReferralClient(baseConfig);
    const msg = client.buildShareMessage('ABC123', 'NomGap');
    expect(msg).toContain('NomGap');
    expect(msg).toContain('refer/ABC123');
  });

  it('should calculate earned days', () => {
    const client = createReferralClient(baseConfig);
    expect(client.calculateEarnedDays(3)).toBe(21);
    expect(client.calculateEarnedDays(0)).toBe(0);
    expect(client.calculateEarnedDays(2, 14)).toBe(28);
  });

  it('should use custom default reward tokens', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockReferral()),
      })
    );

    const client = createReferralClient({
      ...baseConfig,
      defaultRewardTokens: { referrer: 2000, referred: 1000 },
    });
    await client.createReferral({
      referrerId: 'user-1',
      referrerEmail: 'alice@test.com',
      referredEmail: 'bob@test.com',
    });

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
    expect(body.referrerRewardTokens).toBe(2000);
    expect(body.referredRewardTokens).toBe(1000);
  });
});
