// submitPlayerName.test.ts
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import firebaseFunctionsTest from 'firebase-functions-test';

// Declare mock functions using 'let' to allow reassignment
let setMock: ReturnType<typeof vi.fn>;
let getMock: ReturnType<typeof vi.fn>;
let docMock: ReturnType<typeof vi.fn>;
let whereMock: ReturnType<typeof vi.fn>;
let collectionMock: ReturnType<typeof vi.fn>;
let firestoreMock: ReturnType<typeof vi.fn>;

// Initialize mock functions before each test
beforeEach(() => {
  setMock = vi.fn().mockResolvedValue(undefined);
  getMock = vi.fn();
  docMock = vi.fn(() => ({
    id: 'newPlayerId',
    set: setMock,
  }));
  whereMock = vi.fn(() => ({
    get: getMock,
  }));
  collectionMock = vi.fn(() => ({
    where: whereMock,
    doc: docMock,
  }));
  firestoreMock = vi.fn(() => ({
    collection: collectionMock,
  }));
});

describe('submitPlayerName Function', () => {
  let testEnv: any;

  beforeEach(() => {
    // Reset all mocks before each test to ensure isolation
    vi.resetAllMocks();

    // Mock 'firebase-admin' module using vi.doMock inside beforeEach
    vi.doMock('firebase-admin', () => ({
      initializeApp: vi.fn(),
      firestore: firestoreMock,
    }));

    // Initialize firebase-functions-test
    testEnv = firebaseFunctionsTest();
  });

  afterEach(() => {
    // Clean up the test environment after each test
    testEnv.cleanup();

    // Clear all module mocks after each test
    vi.resetModules();
  });

  it('submits a valid player name', async () => {
    // Dynamically import the module under test after mocking
    const { submitPlayerName } = await import('./index');
    const admin = await import('firebase-admin');

    const data = { name: 'JaneDoe' };
    const context = {};

    // Mock Firestore response for duplicate check (no duplicates)
    // playersRef.where('gameId', '==', gameId).where('name', '==', trimmedName).get()
    whereMock
      .mockReturnValueOnce({
        where: whereMock, // First 'where' for 'gameId'
      })
      .mockReturnValueOnce({
        get: getMock, // Second 'where' for 'name'
      });
    getMock.mockResolvedValueOnce({ empty: true });

    // Mock 'doc().set()' to resolve successfully
    docMock.mockReturnValueOnce({
      id: 'newPlayerId',
      set: setMock,
    });

    // Wrap the function using 'firebase-functions-test' to simulate a callable function
    const wrapped = testEnv.wrap(submitPlayerName);

    // Invoke the function
    const result = await wrapped(data, context);

    // Assertions for successful submission
    expect(result).toEqual({ name: 'JaneDoe', playerId: 'newPlayerId' });

    // Ensure Firestore methods were called correctly
    expect(firestoreMock).toHaveBeenCalled();
    expect(admin.firestore).toHaveBeenCalled();
    expect(collectionMock).toHaveBeenCalledWith('players');
    expect(whereMock).toHaveBeenCalledWith('gameId', '==', 'some-game-id');
    expect(whereMock).toHaveBeenCalledWith('name', '==', 'JaneDoe');
    expect(docMock).toHaveBeenCalled();
    expect(setMock).toHaveBeenCalledWith({
      name: 'JaneDoe',
      playerId: 'newPlayerId',
      gameId: 'some-game-id',
      teamId: null,
      jokerUsed: false,
    });
  });

  it('rejects duplicate player name', async () => {
    // Dynamically import the module under test after mocking
    const { submitPlayerName } = await import('./index');
    const admin = await import('firebase-admin');

    const data = { name: 'ExistingPlayer' };
    const context = {};

    // Mock Firestore response for duplicate check (duplicates found)
    whereMock
      .mockReturnValueOnce({
        where: whereMock, // First 'where' for 'gameId'
      })
      .mockReturnValueOnce({
        get: getMock, // Second 'where' for 'name'
      });
    getMock.mockResolvedValueOnce({ empty: false });

    // Wrap the function
    const wrapped = testEnv.wrap(submitPlayerName);

    // Invoke the function and expect it to throw an error
    await expect(wrapped(data, context)).rejects.toThrow('Name already taken in this game.');

    // Ensure Firestore methods were called correctly
    expect(firestoreMock).toHaveBeenCalled();
    expect(admin.firestore).toHaveBeenCalled();
    expect(collectionMock).toHaveBeenCalledWith('players');
    expect(whereMock).toHaveBeenCalledWith('gameId', '==', 'some-game-id');
    expect(whereMock).toHaveBeenCalledWith('name', '==', 'ExistingPlayer');
    expect(getMock).toHaveBeenCalled();
    expect(docMock).not.toHaveBeenCalled();
    expect(setMock).not.toHaveBeenCalled();
  });

  it('rejects invalid player name', async () => {
    // Dynamically import the module under test after mocking
    const { submitPlayerName } = await import('./index');

    const data = { name: 'ab' };
    const context = {};

    // Wrap the function
    const wrapped = testEnv.wrap(submitPlayerName);

    // Invoke the function and expect it to throw a validation error
    await expect(wrapped(data, context)).rejects.toThrow(
      'Name must be 3-20 characters long and can include letters, numbers, spaces, underscores, or hyphens.'
    );

    // Ensure Firestore methods were NOT called
    expect(firestoreMock).not.toHaveBeenCalled();
    expect(collectionMock).not.toHaveBeenCalled();
    expect(whereMock).not.toHaveBeenCalled();
    expect(getMock).not.toHaveBeenCalled();
    expect(docMock).not.toHaveBeenCalled();
    expect(setMock).not.toHaveBeenCalled();
  });

  it('rejects when name is missing', async () => {
    // Dynamically import the module under test after mocking
    const { submitPlayerName } = await import('./index');

    const data = {};
    const context = {};

    // Wrap the function
    const wrapped = testEnv.wrap(submitPlayerName);

    // Invoke the function and expect it to throw an error for missing name
    await expect(wrapped(data, context)).rejects.toThrow('Name is required.');

    // Ensure Firestore methods were NOT called
    expect(firestoreMock).not.toHaveBeenCalled();
    expect(collectionMock).not.toHaveBeenCalled();
    expect(whereMock).not.toHaveBeenCalled();
    expect(getMock).not.toHaveBeenCalled();
    expect(docMock).not.toHaveBeenCalled();
    expect(setMock).not.toHaveBeenCalled();
  });

  it('handles internal server errors gracefully', async () => {
    // Dynamically import the module under test after mocking
    const { submitPlayerName } = await import('./index');
    const admin = await import('firebase-admin');

    const data = { name: 'JaneDoe' };
    const context = {};

    // Mock Firestore to throw an error during the first `where` call
    whereMock.mockImplementationOnce(() => {
      throw new Error('Firestore error');
    });

    // Wrap the function
    const wrapped = testEnv.wrap(submitPlayerName);

    // Invoke the function and expect it to throw an internal server error
    await expect(wrapped(data, context)).rejects.toThrow('Internal Server Error');

    // Ensure Firestore methods were called correctly up to the point of failure
    expect(firestoreMock).toHaveBeenCalled();
    expect(admin.firestore).toHaveBeenCalled();
    expect(collectionMock).toHaveBeenCalledWith('players');
    expect(whereMock).toHaveBeenCalledWith('gameId', '==', 'some-game-id');
    expect(whereMock).not.toHaveBeenCalledWith('name', '==', 'JaneDoe'); // Second 'where' not called
    expect(getMock).not.toHaveBeenCalled();
    expect(docMock).not.toHaveBeenCalled();
    expect(setMock).not.toHaveBeenCalled();
  });
});

---- test results

 FAIL  functions/src/submitPlayerName.test.ts > submitPlayerName Function > submits a valid player name
Unknown Error: Internal Server Error
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯

 FAIL  functions/src/submitPlayerName.test.ts > submitPlayerName Function > rejects duplicate player name
AssertionError: expected [Function] to throw error including 'Name already taken in this game.' but got 'Internal Server Error'

Expected: "Name already taken in this game."
Received: "Internal Server Error"

 ❯ functions/src/submitPlayerName.test.ts:131:5
    129| 
    130|     // Invoke the function and expect it to throw an error
    131|     await expect(wrapped(data, context)).rejects.toThrow('Name already …
       |     ^
    132| 
    133|     // Ensure Firestore methods were called correctly

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯

 FAIL  functions/src/submitPlayerName.test.ts > submitPlayerName Function > handles internal server errors gracefully
AssertionError: expected "spy" to be called at least once
 ❯ functions/src/submitPlayerName.test.ts:212:27
    210| 
    211|     // Ensure Firestore methods were called correctly up to the point o…
    212|     expect(firestoreMock).toHaveBeenCalled();
       |                           ^
    213|     expect(admin.firestore).toHaveBeenCalled();
    214|     expect(collectionMock).toHaveBeenCalledWith('players');

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯

 Test Files  1 failed (1)

----

look at the failing tests. find out why they are failing and fix it