// jest.mock should be at the top of the file
jest.mock('./firebase/config'); // Mock the firebase config module

// Mock FieldValue.serverTimestamp()
jest.mock('firebase-admin/firestore', () => ({
    FieldValue: {
        serverTimestamp: jest.fn(() => 'mocked-timestamp'),
    },
}));

import functionsTest from 'firebase-functions-test';
import { createGame } from './createGame';
import { db, FieldValue } from './firebase/config';

const testEnv = functionsTest();

// Access the mocks from the mocked module
const mockCollection = db.collection as jest.Mock;
const mockFieldValue = FieldValue as unknown as jest.Mock;

describe('createGame', () => {
    let wrapped: any;

    // Declare mockGet and mockAdd at the describe block level
    let mockGet: jest.Mock;
    let mockAdd: jest.Mock;

    beforeAll(() => {
        wrapped = testEnv.wrap(createGame);
    });

    beforeEach(() => {
        jest.resetAllMocks();

        // Initialize mocks
        mockGet = jest.fn();
        mockAdd = jest.fn();

        // Mock db.collection()
        mockCollection.mockImplementation((collectionName: string) => {
            if (collectionName === 'games') {
                return {
                    // For collection('games').where('gameCode', '==', code).get()
                    where: jest.fn().mockImplementation(() => ({
                        get: mockGet,
                    })),
                    // For collection('games').add(newGame)
                    add: mockAdd,
                };
            }
            // Mock other collections if needed
            return {};
        });

        // Default behavior for mockGet and mockAdd
        mockGet.mockResolvedValue({ empty: true }); // No collision by default
        mockAdd.mockResolvedValue({ id: 'game123' });
    });

    afterAll(() => {
        jest.restoreAllMocks();
        testEnv.cleanup();
    });

    it('should successfully create a new game with valid inputs', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        const result = await wrapped(data, context);

        expect(result).toHaveProperty('gameId', 'game123');
        expect(result).toHaveProperty('gameCode');

        // Verify that collection was called with 'games'
        expect(mockCollection).toHaveBeenCalledWith('games');

        // Verify that add was called with expected data
        const expectedGameData = expect.objectContaining({
            gameCode: expect.any(String),
            createdBy: 'TestPlayer',
            roundHost: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
            teams: expect.arrayContaining([
                expect.objectContaining({
                    teamName: 'Team 1',
                    players: expect.arrayContaining([{ name: 'TestPlayer' }]),
                }),
            ]),
            createdAt: 'mocked-timestamp',
            currentRound: 0,
        });

        expect(mockAdd).toHaveBeenCalledWith(expectedGameData);
    });

    it('should throw an unauthenticated error if user is not authenticated', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {}; // No auth

        jest.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'The function must be called while authenticated.'
        );
    });

    it('should throw an invalid-argument error for missing name', async () => {
        const data = {
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        jest.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Name is required and must be between 3 and 20 characters.'
        );
    });

    it('should throw an invalid-argument error for missing numberOfTeams', async () => {
        const data = {
            name: 'TestPlayer',
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of teams is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid playersPerTeam', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: -1,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        jest.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of players per team is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid numberOfRounds', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 0,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        jest.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Number of rounds is required and must be a positive integer.'
        );
    });

    it('should throw an invalid-argument error for invalid jokerUsage', async () => {
        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: 'yes', // Invalid boolean
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        jest.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Joker usage must be a boolean.'
        );
    });

    it('should throw an internal error if Firestore add fails', async () => {
        // Mock add to throw an error
        mockAdd.mockRejectedValueOnce(new Error('Firestore add error'));

        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        jest.spyOn(console, 'error').mockImplementation(() => { });

        await expect(wrapped(data, context)).rejects.toThrow(
            'Unable to create game at this time.'
        );

        // Verify that add was called
        expect(mockAdd).toHaveBeenCalled();
    });

    it('should retry generating a unique game code on collision', async () => {
        // Mock get to return collision on first call, then no collision
        mockGet
            .mockResolvedValueOnce({ empty: false }) // Collision
            .mockResolvedValueOnce({ empty: true }); // No collision

        const data = {
            name: 'TestPlayer',
            numberOfTeams: 2,
            playersPerTeam: 3,
            numberOfRounds: 5,
            jokerUsage: true,
        };
        const context = {
            auth: {
                uid: 'creator-uid',
            },
        };

        const result = await wrapped(data, context);

        expect(result).toHaveProperty('gameId', 'game123');
        expect(result).toHaveProperty('gameCode');

        // Ensure the get method was called twice due to collision retry
        expect(mockGet).toHaveBeenCalledTimes(2);
    });
});

====

import * as functions from 'firebase-functions';
import { db as firestore, FieldValue } from './firebase/config';
import { v4 as uuidv4 } from 'uuid';
import { Game, Team, Player } from './types';

/**
 * Firebase Cloud Function to create a new game session.
 * Initializes a game document in Firestore with specified parameters,
 * generates a unique game code, assigns the creator to a team,
 * and sets them as the initial Round Host.
 */
export const createGame = functions.https.onCall(async (data, context) => {
  // Ensure the user is authenticated (using Anonymous Authentication)
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'The function must be called while authenticated.'
    );
  }

  const { name, numberOfTeams, playersPerTeam, numberOfRounds, jokerUsage } = data;

  // Validate input parameters
  if (
    !name ||
    typeof name !== 'string' ||
    name.trim().length < 3 ||
    name.trim().length > 20
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Name is required and must be between 3 and 20 characters.'
    );
  }

  if (
    !numberOfTeams ||
    typeof numberOfTeams !== 'number' ||
    numberOfTeams <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of teams is required and must be a positive integer.'
    );
  }

  if (
    !playersPerTeam ||
    typeof playersPerTeam !== 'number' ||
    playersPerTeam <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of players per team is required and must be a positive integer.'
    );
  }

  if (
    !numberOfRounds ||
    typeof numberOfRounds !== 'number' ||
    numberOfRounds <= 0
  ) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Number of rounds is required and must be a positive integer.'
    );
  }

  if (typeof jokerUsage !== 'boolean') {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Joker usage must be a boolean.'
    );
  }

  /**
   * Generates a unique game code.
   * Uses a 6-character UUID substring and ensures uniqueness by checking Firestore.
   */
  const generateUniqueGameCode = async (): Promise<string> => {
    const code = uuidv4().slice(0, 6).toUpperCase(); // e.g., 'A1B2C3'
    const existingGame = await firestore
      .collection('games')
      .where('gameCode', '==', code)
      .get();
    if (!existingGame.empty) {
      // Collision detected, retry generating a code
      return generateUniqueGameCode();
    }
    return code;
  };

  try {
    const gameCode = await generateUniqueGameCode();

    const teams: Team[] = Array.from({ length: numberOfTeams }, (_, i) => ({
      teamName: `Team ${i + 1}`,
      players: [],
    }));

    const creatorPlayer: Player = {
      name: name.trim(),
    };

    // Add creator to the first team
    teams[0].players.push(creatorPlayer);

    const newGame: Game = {
      gameCode,
      numberOfTeams,
      playersPerTeam,
      numberOfRounds,
      jokerUsage,
      createdBy: name.trim(),
      roundHost: name.trim(),
      createdAt: FieldValue.serverTimestamp(),
      teams,
      currentRound: 0,
    };

    const gameRef = await firestore.collection('games').add(newGame);

    return { gameId: gameRef.id, gameCode };
  } catch (error) {
    console.error('Error creating game:', error);
    throw new functions.https.HttpsError(
      'internal',
      'Unable to create game at this time.'
    );
  }
});

====

import { DocumentReference, DocumentData, Firestore, CollectionReference } from '@google-cloud/firestore';

// Define a mocked transaction interface
export interface MockedTransaction {
  set: jest.Mock;
  get: jest.Mock;
}

// Define a mocked collection reference interface
export interface MockedCollectionReference {
  doc: jest.Mock<jest.Mocked<DocumentReference<DocumentData>>, [string]>;
  where: jest.Mock;
  get: jest.Mock;
}

// Define a mocked Firestore interface
export interface MockedFirestore {
  collection: jest.Mock<MockedCollectionReference, [string]>;
  runTransaction: jest.Mock<
    Promise<any>,
    [
      (
        transaction: MockedTransaction
      ) => Promise<any>
    ]
  >;
  FieldValue: {
    serverTimestamp: jest.Mock<Date, []>;
  };
  doc: jest.Mock<jest.Mocked<DocumentReference<DocumentData>>, [string]>;
}

// Mock implementations for query methods
const whereFunction = jest.fn().mockReturnThis();
const getFunction = jest.fn().mockResolvedValue({ empty: true });

// Define a mocked DocumentReference
const docMock: jest.Mocked<DocumentReference<DocumentData>> = {
  id: 'test-game-id',
  firestore: {} as jest.Mocked<Firestore>,
  parent: {} as jest.Mocked<CollectionReference<DocumentData>>,
  path: 'games/test-game-id',
  collection: jest.fn(),
  set: jest.fn(),
  update: jest.fn(),
  delete: jest.fn(),
  get: jest.fn(),
  onSnapshot: jest.fn(),
  withConverter: jest.fn(),
  // Add the missing methods
  listCollections: jest.fn(),
  create: jest.fn(),
  isEqual: jest.fn(),
};

// Mock Firestore collection
const collection = jest.fn<MockedCollectionReference, [string]>(() => ({
  doc: jest.fn().mockReturnValue(docMock),
  where: whereFunction,
  get: getFunction,
}));

// Mock runTransaction
const runTransaction = jest.fn().mockImplementation(async (
  updateFunction: (transaction: MockedTransaction) => Promise<any>
) => {
  const transaction: MockedTransaction = {
    set: jest.fn(),
    get: jest.fn().mockResolvedValue({ data: () => ({}) }),
  };
  return updateFunction(transaction);
});

// Mock FieldValue.serverTimestamp
const serverTimestamp = jest.fn(() => new Date());

// Mock Firestore instance
const firestore: MockedFirestore = {
  collection,
  runTransaction,
  FieldValue: {
    serverTimestamp,
  },
  doc: jest.fn().mockReturnValue(docMock),
};

// Mock admin SDK
const admin = {
  apps: [],
  initializeApp: jest.fn(),
  firestore: jest.fn(() => firestore),
};

export default admin;


====

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleNameMapper: {
    '^firebase-admin$': '<rootDir>/__mocks__/firebase-admin.ts', // Mock firebase-admin
    '^firebase-functions$': '<rootDir>/node_modules/firebase-functions', // Point to actual firebase-functions
  },
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], // Jest setup file
  transform: {
    '^.+\\.ts?$': [
      'ts-jest',
      {
        tsconfig: 'tsconfig.json', // Specify your TypeScript configuration
      },
    ],
  },
};

===

Fix the test. and are the mocs used and how?