// Integration tests for garbage system

const {
  BOARD_WIDTH,
  BOARD_HEIGHT,
  GARBAGE_COLOR,
  createEmptyBoard,
  getPieceFromType,
  checkBoardOverflow,
  addGarbageRow,
  lockPieceToBoard
} = require('../game-logic');

// Mock player object
function createMockPlayer(name, pieceY = 5) {
  return {
    id: `player-${name}`,
    name,
    board: createEmptyBoard(),
    currentPiece: pieceY !== null ? { ...getPieceFromType('I'), y: pieceY } : null,
    nextPiece: null,
    holdPiece: null,
    canHold: true,
    score: 0,
    lines: 0,
    level: 1,
    eliminated: false,
    dropCounter: 0,
    dropInterval: 1000,
    garbageReceived: []
  };
}

// Mock lobby object
function createMockLobby(players) {
  const lobby = {
    players: new Map()
  };
  players.forEach(p => lobby.players.set(p.id, p));
  return lobby;
}

// Simulate addGarbageToPlayer (from index.js)
function addGarbageToPlayer(player, senderName) {
  const result = addGarbageRow(player.board, player.currentPiece);
  player.board = result.board;
  player.currentPiece = result.currentPiece;
  player.garbageReceived.push({ rows: 1, sender: senderName });
}

// Simulate sendGarbage (from index.js)
function sendGarbage(lobby, sender, rowsCleared) {
  const garbageLog = [];
  for (let i = 0; i < rowsCleared; i++) {
    const opponents = Array.from(lobby.players.values()).filter(
      p => p.id !== sender.id && !p.eliminated
    );
    if (opponents.length === 0) {
      break;
    }
    const target = opponents[Math.floor(Math.random() * opponents.length)];
    garbageLog.push(target.name);
    addGarbageToPlayer(target, sender.name);
  }
  return garbageLog;
}

// Simulate lockPiece with elimination check (from index.js)
function lockPiece(player) {
  if (!player.currentPiece) return 0;

  // Lock piece into board
  player.board = lockPieceToBoard(player.currentPiece, player.board);

  // Check board overflow for elimination
  if (checkBoardOverflow(player.board)) {
    player.eliminated = true;
    return 0;
  }

  // Clear rows
  let rowsCleared = 0;
  for (let row = BOARD_HEIGHT - 1; row >= 0; row--) {
    if (player.board[row].every(cell => cell !== 0)) {
      player.board.splice(row, 1);
      player.board.unshift(Array(BOARD_WIDTH).fill(0));
      rowsCleared++;
      row++;
    }
  }

  return rowsCleared;
}

describe('addGarbageToPlayer', () => {
  test('adds exactly 1 garbage row per call', () => {
    const player = createMockPlayer('Player1', 5);
    const initialGarbageCount = player.garbageReceived.length;

    addGarbageToPlayer(player, 'Enemy');

    expect(player.garbageReceived.length).toBe(initialGarbageCount + 1);
    expect(player.garbageReceived[0].rows).toBe(1);
  });

  test('does NOT eliminate player when receiving garbage', () => {
    const player = createMockPlayer('Player1', 5);

    addGarbageToPlayer(player, 'Enemy');

    expect(player.eliminated).toBe(false);
  });

  test('pushes current piece up by 1 row', () => {
    const player = createMockPlayer('Player1', 10);

    addGarbageToPlayer(player, 'Enemy');

    expect(player.currentPiece.y).toBe(9);
  });

  test('tracks sender of garbage', () => {
    const player = createMockPlayer('Player1', 5);

    addGarbageToPlayer(player, 'Attacker');

    expect(player.garbageReceived[0].sender).toBe('Attacker');
  });

  test('player survives multiple garbage rows if board not full', () => {
    const player = createMockPlayer('Player1', 10);

    // Receive 5 garbage rows
    for (let i = 0; i < 5; i++) {
      addGarbageToPlayer(player, 'Enemy');
    }

    // Player should still be alive
    expect(player.eliminated).toBe(false);
    expect(player.currentPiece.y).toBe(5); // 10 - 5 = 5
  });

  test('handles player with no current piece', () => {
    const player = createMockPlayer('Player1', null);

    addGarbageToPlayer(player, 'Enemy');

    expect(player.currentPiece).toBe(null);
    expect(player.garbageReceived.length).toBe(1);
  });

  test('multiple garbage from different senders tracked separately', () => {
    const player = createMockPlayer('Player1', 10);

    addGarbageToPlayer(player, 'Enemy1');
    addGarbageToPlayer(player, 'Enemy2');
    addGarbageToPlayer(player, 'Enemy1');

    expect(player.garbageReceived).toHaveLength(3);
    expect(player.garbageReceived[0].sender).toBe('Enemy1');
    expect(player.garbageReceived[1].sender).toBe('Enemy2');
    expect(player.garbageReceived[2].sender).toBe('Enemy1');
  });
});

describe('sendGarbage', () => {
  test('sends garbage equal to rows cleared', () => {
    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    const garbageLog = sendGarbage(lobby, sender, 3);

    expect(garbageLog.length).toBe(3);
    expect(target.garbageReceived.length).toBe(3);
  });

  test('excludes sender from garbage targets', () => {
    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    sendGarbage(lobby, sender, 2);

    expect(sender.garbageReceived.length).toBe(0);
    expect(target.garbageReceived.length).toBe(2);
  });

  test('excludes eliminated players from garbage targets', () => {
    const sender = createMockPlayer('Sender', 5);
    const eliminated = createMockPlayer('Eliminated', 10);
    eliminated.eliminated = true;
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, eliminated, target]);

    sendGarbage(lobby, sender, 3);

    expect(eliminated.garbageReceived.length).toBe(0);
    expect(target.garbageReceived.length).toBe(3);
  });

  test('stops sending when all opponents eliminated', () => {
    const sender = createMockPlayer('Sender', 5);
    const eliminated = createMockPlayer('Eliminated', 10);
    eliminated.eliminated = true;
    const lobby = createMockLobby([sender, eliminated]);

    const garbageLog = sendGarbage(lobby, sender, 4);

    expect(garbageLog.length).toBe(0);
  });

  test('distributes garbage to multiple opponents', () => {
    const sender = createMockPlayer('Sender', 5);
    const target1 = createMockPlayer('Target1', 10);
    const target2 = createMockPlayer('Target2', 10);
    const lobby = createMockLobby([sender, target1, target2]);

    sendGarbage(lobby, sender, 4);

    // Total garbage sent should equal rows cleared
    expect(target1.garbageReceived.length + target2.garbageReceived.length).toBe(4);
  });

  test('garbage distribution is deterministic with mocked random', () => {
    const sender = createMockPlayer('Sender', 5);
    const target1 = createMockPlayer('Target1', 10);
    const target2 = createMockPlayer('Target2', 10);
    const lobby = createMockLobby([sender, target1, target2]);

    // Mock Math.random to always pick first opponent (index 0)
    const originalRandom = Math.random;
    Math.random = () => 0.1;

    sendGarbage(lobby, sender, 4);

    Math.random = originalRandom;

    // All garbage should go to target1 (first in array)
    expect(target1.garbageReceived.length).toBe(4);
    expect(target2.garbageReceived.length).toBe(0);
  });

  test('1 row cleared sends 1 garbage row', () => {
    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    const garbageLog = sendGarbage(lobby, sender, 1);

    expect(garbageLog.length).toBe(1);
    expect(target.garbageReceived.length).toBe(1);
  });

  test('2 rows cleared sends 2 garbage rows', () => {
    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    const garbageLog = sendGarbage(lobby, sender, 2);

    expect(garbageLog.length).toBe(2);
  });

  test('4 rows (tetris) sends 4 garbage rows', () => {
    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    const garbageLog = sendGarbage(lobby, sender, 4);

    expect(garbageLog.length).toBe(4);
  });
});

describe('lockPiece elimination', () => {
  test('piece locking does not eliminate when board not overflowed', () => {
    const player = createMockPlayer('Player1', 10);

    const rowsCleared = lockPiece(player);

    expect(player.eliminated).toBe(false);
    expect(rowsCleared).toBe(0);
  });

  test('piece locking eliminates when board overflowed', () => {
    const player = createMockPlayer('Player1', 0);
    // Fill top 2 rows with garbage
    player.board[0].fill(GARBAGE_COLOR);
    player.board[0][5] = 0; // Gap

    lockPiece(player);

    expect(player.eliminated).toBe(true);
  });

  test('piece at y=0 eliminates when it locks into top rows', () => {
    const player = createMockPlayer('Player1', 0);
    // I piece at y=0 locks into rows 0,1,2,3
    // This causes overflow since row 0 and 1 will have blocks

    lockPiece(player);

    expect(player.eliminated).toBe(true);
  });

  test('piece at y=5 does not eliminate if top rows empty', () => {
    const player = createMockPlayer('Player1', 5);
    // Top rows are empty, piece locks at rows 5,6,7,8

    lockPiece(player);

    expect(player.eliminated).toBe(false);
  });

  test('piece locks and does not clear rows when no complete rows', () => {
    const player = createMockPlayer('Player1', 10);
    // Piece locks at rows 10,11,12,13 - no complete rows

    const rowsCleared = lockPiece(player);

    expect(rowsCleared).toBe(0);
  });
});

describe('checkBoardOverflow', () => {
  test('returns false when top 2 rows are empty', () => {
    const board = createEmptyBoard();
    expect(checkBoardOverflow(board)).toBe(false);
  });

  test('returns true when row 0 has any block', () => {
    const board = createEmptyBoard();
    board[0][5] = '#ff0000';
    expect(checkBoardOverflow(board)).toBe(true);
  });

  test('returns true when row 1 has any block', () => {
    const board = createEmptyBoard();
    board[1][5] = '#ff0000';
    expect(checkBoardOverflow(board)).toBe(true);
  });

  test('returns false when only row 2 has blocks', () => {
    const board = createEmptyBoard();
    board[2][5] = '#ff0000';
    expect(checkBoardOverflow(board)).toBe(false);
  });

  test('returns false when garbage is at bottom rows only', () => {
    const board = createEmptyBoard();
    board[19].fill(GARBAGE_COLOR);
    board[18].fill(GARBAGE_COLOR);
    expect(checkBoardOverflow(board)).toBe(false);
  });

  test('returns true when both row 0 and row 1 have blocks', () => {
    const board = createEmptyBoard();
    board[0][3] = '#ff0000';
    board[1][7] = '#ff0000';
    expect(checkBoardOverflow(board)).toBe(true);
  });

  test('returns true when row 0 has garbage with gap', () => {
    const board = createEmptyBoard();
    board[0].fill(GARBAGE_COLOR);
    board[0][5] = 0; // Gap in row 0
    expect(checkBoardOverflow(board)).toBe(true);
  });
});

describe('Garbage Elimination Bug Fix', () => {
  test('player NOT eliminated immediately after receiving garbage', () => {
    // This tests the bug fix: players should NOT be eliminated when garbage is received
    // Elimination should only happen when piece locks and board overflows

    const player = createMockPlayer('Player1', 5);

    // Receive garbage - player should NOT be eliminated
    addGarbageToPlayer(player, 'Enemy');

    expect(player.eliminated).toBe(false);
  });

  test('player survives garbage when piece is pushed but still visible', () => {
    // Player with piece at y=5 receives 3 garbage rows
    // Piece should be at y=2, still visible, player not eliminated

    const player = createMockPlayer('Player1', 5);

    for (let i = 0; i < 3; i++) {
      addGarbageToPlayer(player, 'Enemy');
    }

    expect(player.eliminated).toBe(false);
    expect(player.currentPiece.y).toBe(2);
  });

  test('multiple garbage rows do not cause premature elimination', () => {
    // The original bug caused elimination even when board wasn't full
    // This test verifies that garbage alone doesn't eliminate

    const player = createMockPlayer('Player1', 8);

    // Receive 5 garbage rows - piece pushed to y=3
    for (let i = 0; i < 5; i++) {
      addGarbageToPlayer(player, 'Enemy');
    }

    // Board has 5 garbage rows at bottom, piece at y=3
    // Player should NOT be eliminated
    expect(player.eliminated).toBe(false);
    expect(checkBoardOverflow(player.board)).toBe(false);
  });

  test('sendGarbage does not eliminate opponents', () => {
    // When sender clears rows, opponents receive garbage but should not be eliminated

    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    // Sender clears 4 rows (tetris)
    sendGarbage(lobby, sender, 4);

    // Target received 4 garbage rows but should NOT be eliminated
    expect(target.eliminated).toBe(false);
    expect(target.garbageReceived.length).toBe(4);
  });

  test('elimination only happens when piece locks with overflow', () => {
    // Complete scenario: garbage received, piece locks, then elimination checked

    const player = createMockPlayer('Player1', 0); // Piece at spawn

    // Receive 1 garbage row - player NOT eliminated
    addGarbageToPlayer(player, 'Enemy');
    expect(player.eliminated).toBe(false);

    // Fill top row to simulate overflow
    player.board[0].fill(GARBAGE_COLOR);
    player.board[0][5] = 0;

    // Now lock piece - player eliminated
    lockPiece(player);
    expect(player.eliminated).toBe(true);
  });

  test('player with piece above board survives garbage', () => {
    // Edge case: piece is already above visible area (negative y)
    // Should still not be eliminated by garbage alone

    const player = createMockPlayer('Player1', 2);

    // Push piece 5 rows up (to y=-3)
    for (let i = 0; i < 5; i++) {
      addGarbageToPlayer(player, 'Enemy');
    }

    // Piece is at y=-3, but player NOT eliminated
    expect(player.eliminated).toBe(false);
    expect(player.currentPiece.y).toBe(-3);
  });
});

describe('edge cases', () => {
  test('player with piece at y=0 receives garbage', () => {
    const player = createMockPlayer('Player1', 0);

    addGarbageToPlayer(player, 'Enemy');

    expect(player.currentPiece.y).toBe(-1);
    expect(player.eliminated).toBe(false);
  });

  test('18 garbage rows pushes piece well above board', () => {
    const player = createMockPlayer('Player1', 10);

    for (let i = 0; i < 18; i++) {
      addGarbageToPlayer(player, 'Enemy');
    }

    expect(player.currentPiece.y).toBe(-8);
    // Board overflowed but player not eliminated yet (only on lock)
    expect(player.eliminated).toBe(false);
  });

  test('garbage with no opponents does not crash', () => {
    const sender = createMockPlayer('Sender', 5);
    const lobby = createMockLobby([sender]);

    const garbageLog = sendGarbage(lobby, sender, 4);

    expect(garbageLog.length).toBe(0);
  });

  test('zero rows cleared sends no garbage', () => {
    const sender = createMockPlayer('Sender', 5);
    const target = createMockPlayer('Target', 10);
    const lobby = createMockLobby([sender, target]);

    const garbageLog = sendGarbage(lobby, sender, 0);

    expect(garbageLog.length).toBe(0);
    expect(target.garbageReceived.length).toBe(0);
  });
});
