// 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 whereNameMock: ReturnType<typeof vi.fn>;
let whereGameIdMock: 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,
  }));
  whereNameMock = vi.fn(() => ({
    get: getMock,
  }));
  whereGameIdMock = vi.fn(() => ({
    where: whereNameMock,
  }));
  collectionMock = vi.fn(() => ({
    where: whereGameIdMock,
    doc: docMock,
  }));
  firestoreMock = vi.fn(() => ({
    collection: collectionMock,
  }));
});

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

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

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

    // 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)
    whereGameIdMock.mockReturnValueOnce({
      where: whereNameMock,
    });
    whereNameMock.mockReturnValueOnce({
      get: getMock,
    });
    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(whereGameIdMock).toHaveBeenCalledWith('gameId', '==', 'some-game-id');
    expect(whereNameMock).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)
    whereGameIdMock.mockReturnValueOnce({
      where: whereNameMock,
    });
    whereNameMock.mockReturnValueOnce({
      get: getMock,
    });
    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(whereGameIdMock).toHaveBeenCalledWith('gameId', '==', 'some-game-id');
    expect(whereNameMock).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(whereGameIdMock).not.toHaveBeenCalled();
    expect(whereNameMock).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(whereGameIdMock).not.toHaveBeenCalled();
    expect(whereNameMock).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
    whereGameIdMock.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(whereGameIdMock).toHaveBeenCalledWith('gameId', '==', 'some-game-id');
    expect(whereNameMock).not.toHaveBeenCalled();
    expect(getMock).not.toHaveBeenCalled();
    expect(docMock).not.toHaveBeenCalled();
    expect(setMock).not.toHaveBeenCalled();
  });
});

// index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { Player } from '../../src/types'; // Adjust the path as necessary

admin.initializeApp();

export const submitPlayerName = functions.https.onCall(async (data, context) => {
  const { name } = data;

  // Server-side validation
  if (!name || typeof name !== 'string' || name.trim().length === 0) {
    throw new functions.https.HttpsError('invalid-argument', 'Name is required.');
  }

  const trimmedName = name.trim();
  const nameRegex = /^[a-zA-Z0-9 _-]{3,20}$/;

  if (!nameRegex.test(trimmedName)) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Name must be 3-20 characters long and can include letters, numbers, spaces, underscores, or hyphens.'
    );
  }

  try {
    const firestore = admin.firestore();

    // TODO: Determine how to retrieve or assign gameId
    const gameId = 'some-game-id'; // Replace with actual gameId retrieval logic

    // Check for duplicate names within the same game session
    const playersRef = firestore.collection('players');
    const duplicateSnapshot = await playersRef
      .where('gameId', '==', gameId)
      .where('name', '==', trimmedName)
      .get();

    if (!duplicateSnapshot.empty) {
      throw new functions.https.HttpsError('already-exists', 'Name already taken in this game.');
    }

    // Create a new player document
    const newPlayerRef = playersRef.doc();
    const newPlayer: Player = {
      playerId: newPlayerRef.id,
      name: trimmedName,
      gameId,
      teamId: null,
      jokerUsed: false,
    };

    await newPlayerRef.set(newPlayer);

    return { name: trimmedName, playerId: newPlayerRef.id };
  } catch (error) {
    console.error('Error submitting player name:', error);
    if (error instanceof functions.https.HttpsError) {
      throw error;
    }
    throw new functions.https.HttpsError('internal', 'Internal Server Error');
  }
});

fix the tests