// Character writeback on endEncounter: sync maxHp/ac to campaign players.
const shared = require('@ttrpg/shared');
const { endEncounter } = shared;

function makeMockStorage() {
  const docs = new Map();
  const writes = [];
  return {
    getDoc: jest.fn(async (p) => docs.get(p) || null),
    setDoc: jest.fn(async (p, d) => { docs.set(p, d); }),
    updateDoc: jest.fn(async (p, patch) => {
      writes.push({ path: p, patch });
      const cur = docs.get(p) || {};
      docs.set(p, { ...cur, ...patch });
    }),
    addDoc: jest.fn(async (p, data) => {
      writes.push({ path: p, patch: data });
      docs.set(p, data);
    }),
    deleteDoc: jest.fn(async () => {}),
    getCollection: jest.fn(async () => []),
    _docs: docs,
    _writes: writes,
  };
}

function makeEncounter(overrides = {}) {
  return {
    id: 'enc1',
    name: 'Test',
    campaignId: 'camp1',
    isStarted: true,
    isPaused: false,
    currentTurnParticipantId: 'p1',
    round: 3,
    turnOrderIds: ['p1', 'p2'],
    participants: [
      { id: 'p1', name: 'Fighter', type: 'character', originalCharacterId: 'char1', initiative: 15, maxHp: 28, currentHp: 20, ac: 18 },
      { id: 'p2', name: 'Goblin', type: 'monster', originalCharacterId: null, initiative: 12, maxHp: 7, currentHp: 7, ac: 13 },
      { id: 'p3', name: 'Cleric', type: 'character', originalCharacterId: 'char2', initiative: 10, maxHp: 22, currentHp: 22, ac: 16 },
    ],
    ...overrides,
  };
}

describe('character writeback on endEncounter', () => {
  test('no writeback when syncCharacters false/missing', async () => {
    const storage = makeMockStorage();
    storage._docs.set('campaigns/camp1', {
      id: 'camp1', name: 'Camp', players: [
        { id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17 },
      ],
    });
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      logPath: 'logs/log1',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    await endEncounter(enc, ctx);
    // no campaign write
    expect(storage._writes.find(w => w.path === 'campaigns/camp1')).toBeUndefined();
  });

  test('writeback updates char maxHp + ac + currentHp when syncCharacters true', async () => {
    const storage = makeMockStorage();
    const players = [
      { id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17, defaultCurrentHp: 30 },
      { id: 'char2', name: 'Cleric', defaultMaxHp: 20, defaultAc: 14, defaultCurrentHp: 20 },
    ];
    storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players, syncCharacters: true });
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      logPath: 'logs/log1',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    await endEncounter(enc, ctx);
    const campWrite = storage._writes.find(w => w.path === 'campaigns/camp1');
    expect(campWrite).toBeDefined();
    const updated = campWrite.patch.players;
    const fighter = updated.find(p => p.id === 'char1');
    expect(fighter.defaultMaxHp).toBe(28);
    expect(fighter.defaultAc).toBe(18);
    expect(fighter.defaultCurrentHp).toBe(20);
    const cleric = updated.find(p => p.id === 'char2');
    expect(cleric.defaultMaxHp).toBe(22);
    expect(cleric.defaultAc).toBe(16);
    expect(cleric.defaultCurrentHp).toBe(22);
  });

  test('writeback skips monsters (no originalCharacterId)', async () => {
    const storage = makeMockStorage();
    const players = [{ id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17 }];
    storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players, syncCharacters: true });
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      logPath: 'logs/log1',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    await endEncounter(enc, ctx);
    const campWrite = storage._writes.find(w => w.path === 'campaigns/camp1');
    // players array still 1 entry, goblin not added
    expect(campWrite.patch.players.length).toBe(1);
  });

  test('undo payload includes old char values for restore', async () => {
    const storage = makeMockStorage();
    const players = [
      { id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17, defaultCurrentHp: 30 },
      { id: 'char2', name: 'Cleric', defaultMaxHp: 20, defaultAc: 14, defaultCurrentHp: 20 },
    ];
    storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players, syncCharacters: true });
    storage._docs.set('logs/log1', null);
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      logPath: 'logs/log1',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    await endEncounter(enc, ctx);
    const logWrite = storage._writes.find(w => w.path === 'logs/log1');
    expect(logWrite).toBeDefined();
    const log = logWrite.patch;
    expect(log.undo.characterWriteback).toBeDefined();
    expect(log.undo.characterWriteback).toHaveLength(2);
    const fighterOld = log.undo.characterWriteback.find(c => c.id === 'char1');
    expect(fighterOld.defaultMaxHp).toBe(30);
    expect(fighterOld.defaultAc).toBe(17);
    expect(fighterOld.defaultCurrentHp).toBe(30);
  });

  test('writeback uses ctx.campaignPath when provided (firebase prefixed paths)', async () => {
    const storage = makeMockStorage();
    const players = [{ id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17, defaultCurrentHp: 30 }];
    const prefixed = 'artifacts/app1/public/data/campaigns/camp1';
    storage._docs.set(prefixed, { id: 'camp1', name: 'Camp', players, syncCharacters: true });
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      campaignPath: prefixed,
      logPath: 'logs/log1',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    await endEncounter(enc, ctx);
    expect(storage._writes.find(w => w.path === 'campaigns/camp1')).toBeUndefined();
    const campWrite = storage._writes.find(w => w.path === prefixed);
    expect(campWrite).toBeDefined();
    expect(campWrite.patch.players.find(p => p.id === 'char1').defaultMaxHp).toBe(28);
  });

  test('writeback failure does not block ending combat', async () => {
    const storage = makeMockStorage();
    const players = [{ id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17, defaultCurrentHp: 30 }];
    storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players, syncCharacters: true });
    storage.updateDoc = jest.fn(async (p, patch) => {
      if (p === 'campaigns/camp1') throw new Error('No document to update');
      storage._writes.push({ path: p, patch });
    });
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      encPath: 'campaigns/camp1/encounters/enc1',
      logPath: 'logs',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    const newEnc = await endEncounter(enc, ctx);
    expect(newEnc.isStarted).toBe(false);
    expect(newEnc.currentTurnParticipantId).toBe(null);
    // participants are preserved — ending combat never clears the roster
    expect(newEnc.participants).toHaveLength(3);
    const encWrite = storage._writes.find(w => w.path === 'campaigns/camp1/encounters/enc1');
    expect(encWrite).toBeDefined();
    expect(encWrite.patch.isStarted).toBe(false);
    expect(encWrite.patch.participants).toBeUndefined();
  });

  test('writeback no-op if character missing from campaign', async () => {
    const storage = makeMockStorage();
    storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players: [], syncCharacters: true });
    const enc = makeEncounter();
    const ctx = {
      storage,
      encounterPath: 'campaigns/camp1/encounters/enc1',
      logPath: 'logs/log1',
      logCollection: 'logs',
      displayPath: 'activeDisplay/status',
    };
    await endEncounter(enc, ctx);
    // no writeback (no matching chars = no change)
    expect(storage._writes.find(w => w.path === 'campaigns/camp1')).toBeUndefined();
  });
});
