// Combat characterization. Lock updateDoc/setDoc patch for combat controls.

import React from 'react';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { getCalls } from '../__mocks__/firebase/_mock-db';
import { setupReady, addMonsterViaUI, startCombatViaUI } from './testHelpers';

function findCallsEnc() {
  return getCalls().filter(c => c.fn === 'updateDoc' && c.path.includes('/encounters/'));
}
function lastEncCall() {
  const calls = findCallsEnc();
  return calls[calls.length - 1];
}
function findCallActiveDisplay(fn) {
  return getCalls().filter(c => c.fn === fn && c.path.includes('activeDisplay/status'));
}

async function setupWithMonsters(names = ['A', 'B', 'C']) {
  await setupReady('CombatCamp', 'CombatEnc');
  for (const n of names) {
    await addMonsterViaUI(n, 20, Number(n.charCodeAt(0) % 10));
  }
}

describe('Combat -> Firebase', () => {
  test('startEncounter: updateDoc sets isStarted/round/turn/current', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    const call = lastEncCall();
    expect(call.data).toMatchObject({
      isStarted: true,
      isPaused: false,
      round: 1,
    });
    expect(call.data.currentTurnParticipantId).toBeTruthy();
    expect(call.data.turnOrderIds).toHaveLength(3);
  });

  test('startEncounter: also sets activeDisplay to this encounter', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    const adCalls = findCallActiveDisplay('setDoc');
    const last = adCalls[adCalls.length - 1];
    expect(last.data.activeCampaignId).toBeTruthy();
    expect(last.data.activeEncounterId).toBeTruthy();
  });

  test('nextTurn: advances currentTurnParticipantId', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    const beforeId = lastEncCall().data.currentTurnParticipantId;

    fireEvent.click(screen.getByRole('button', { name: /Next Turn/i }));
    await waitFor(() => lastEncCall()?.data?.currentTurnParticipantId !== beforeId);
    expect(lastEncCall().data.currentTurnParticipantId).not.toBe(beforeId);
  });

  test('nextTurn wrapping to round 1->2 increments round', async () => {
    await setupWithMonsters(['A', 'B']);
    await startCombatViaUI();

    // advance through all participants to wrap
    fireEvent.click(screen.getByRole('button', { name: /Next Turn/i })); // A->B (or 2nd)
    await waitFor(() => lastEncCall()?.data?.currentTurnParticipantId);
    fireEvent.click(screen.getByRole('button', { name: /Next Turn/i })); // wrap
    await waitFor(() => lastEncCall()?.data?.round === 2);
    expect(lastEncCall().data.round).toBe(2);
  });

  test('pause: updateDoc sets isPaused true', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    fireEvent.click(screen.getByRole('button', { name: /Pause Combat/i }));
    await waitFor(() => lastEncCall()?.data?.isPaused === true);
    expect(lastEncCall().data.isPaused).toBe(true);
  });

  test('resume: updateDoc sets isPaused false + recomputes turnOrder', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    fireEvent.click(screen.getByRole('button', { name: /Pause Combat/i }));
    await waitFor(() => lastEncCall()?.data?.isPaused === true);
    fireEvent.click(screen.getByRole('button', { name: /Resume Combat/i }));
    await waitFor(() => lastEncCall()?.data?.isPaused === false);
    const call = lastEncCall();
    expect(call.data.isPaused).toBe(false);
    expect(call.data.turnOrderIds).toHaveLength(3);
  });

  test('endEncounter: updateDoc resets all combat state', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    fireEvent.click(screen.getByRole('button', { name: /End Combat/i }));
    fireEvent.click(await screen.findByRole('button', { name: /Confirm/i }));
    await waitFor(() => lastEncCall()?.data?.isStarted === false);
    const call = lastEncCall();
    expect(call.data).toMatchObject({
      isStarted: false,
      isPaused: false,
      round: 0,
      currentTurnParticipantId: null,
      turnOrderIds: [],
    });
  });

  test('endEncounter: clears activeDisplay', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    fireEvent.click(screen.getByRole('button', { name: /End Combat/i }));
    fireEvent.click(await screen.findByRole('button', { name: /Confirm/i }));
    await waitFor(() => {
      const adCalls = findCallActiveDisplay('setDoc');
      const last = adCalls[adCalls.length - 1];
      return last && last.data.activeCampaignId === null;
    });
    const adCalls = findCallActiveDisplay('setDoc');
    const last = adCalls[adCalls.length - 1];
    expect(last.data).toMatchObject({ activeCampaignId: null, activeEncounterId: null });
  });

  test('toggleHidePlayerHp: setDoc{merge} patch on activeDisplay/status', async () => {
    await setupWithMonsters();
    await startCombatViaUI();
    // Two switches now (Hide player HP + Hide NPC/monster HP). Scope to player one.
    const playerHpLabel = screen.getByText('Hide player HP');
    const switchBtn = playerHpLabel.parentElement.querySelector('[role="switch"]');
    fireEvent.click(switchBtn);
    await waitFor(() => {
      const adCalls = findCallActiveDisplay('setDoc');
      const last = adCalls[adCalls.length - 1];
      return last && 'hidePlayerHp' in last.data;
    });
    const adCalls = findCallActiveDisplay('setDoc');
    const last = adCalls[adCalls.length - 1];
    expect(last.data).toHaveProperty('hidePlayerHp');
  });
});
