/**
 * Epic 2: Player Notifications & Director's Board - Tests E2E
 * 
 * FR-9: GM opens Director's Board via sidebar button + keyboard shortcut
 * FR-10: Director's Board displays full Visibility Matrix in seating-chart layout
 * FR-11: Per-Participant visibility toggle from Director's Board
 * FR-12: Bulk Show All / Hide All with one-step Undo
 * FR-13: Spotlight action with pre-spotlight snapshot and Restore
 * FR-14: Full keyboard shortcuts for Director's Board actions
 * FR-20: Toast notification to all participants on GM visibility change
 * FR-21: Notification verbosity configuration per user
 */

import { test, expect } from '@playwright/test';
import {
  waitForFoundryReady,
  waitForVVMModule,
  openDirectorsBoard,
  clickFoundryButton,
  toggleParticipantInBoard,
  waitForNotification,
  hideParticipant,
  showParticipant,
} from '../utils/foundry-helpers';

const TEST_PLAYERS = ['Player1', 'Player2', 'Player3'];

test.describe('Epic 2: Player Notifications & Director\'s Board', () => {
  test.setTimeout(120000);

  test.beforeEach(async ({ page }) => {
    await page.goto('https://localhost:31000', {
      waitUntil: 'domcontentloaded',
      timeout: 30000,
    });
    
    await waitForFoundryReady(page);
    await waitForVVMModule(page);
    
    const isGM = await page.evaluate(() => game.user?.isGM || false);
    if (!isGM) {
      test.skip(true, 'Test requires GM user');
    }
  });

  // ============================================================================
  // FR-9: GM opens Director's Board via sidebar button
  // ============================================================================
  test.describe('FR-9: Director\'s Board Access', () => {
    test('GM can open Director\'s Board via sidebar button', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Vérifier que le board est visible
      await page.waitForSelector('.scrying-pool-directors-board', {
        state: 'visible',
        timeout: 10000,
      });
    });

    test('Director\'s Board is a resizable, draggable ApplicationV2 window', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const board = page.locator('.scrying-pool-directors-board');
      
      // Vérifier qu'il a la classe window-content (ApplicationV2)
      await expect(board).toHaveClass(/window-content/);
      
      // Vérifier qu'il est redimensionnable
      const resizeHandle = board.locator('.window-resizable-handle');
      await expect(resizeHandle).toBeVisible();
    });

    test('Director\'s Board opens as floating window', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const board = page.locator('.scrying-pool-directors-board');
      
      // Vérifier la position (devrait être flottante, pas fixed)
      const position = await board.evaluate(el => {
        return window.getComputedStyle(el).position;
      });
      
      expect(position).toBe('absolute');
    });
  });

  // ============================================================================
  // FR-10: Director's Board displays full Visibility Matrix
  // ============================================================================
  test.describe('FR-10: Visibility Matrix Display', () => {
    test('Board displays all connected participants in seating-chart layout', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Attendre que les cartes des participants soient chargées
      await page.waitForSelector('.sp-participant-card', {
        state: 'visible',
        timeout: 10000,
      });
      
      // Vérifier qu'il y a des cartes de participants
      const cards = page.locator('.sp-participant-card');
      const count = await cards.count();
      
      expect(count).toBeGreaterThan(0);
    });

    test('Each participant card shows name, portrait, current state', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const card = page.locator('.sp-participant-card').first();
      
      // Vérifier le nom
      await expect(card.locator('.sp-participant-name')).toBeVisible();
      
      // Vérifier le portrait
      await expect(card.locator('.sp-participant-avatar')).toBeVisible();
      
      // Vérifier l'état
      await expect(card.locator('.sp-state-badge')).toBeVisible();
    });

    test('Visibility State updates appear within 500ms', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const startTime = Date.now();
      
      // Changer l'état via le context menu
      await hideParticipant(page, 'Player1');
      
      // Attendre la mise à jour dans le board
      await page.waitForFunction((playerName) => {
        const card = document.querySelector('.sp-participant-card:has-text("Player1")');
        if (!card) return false;
        
        const badge = card.querySelector('.sp-state-badge');
        if (!badge) return false;
        
        return badge.textContent.includes('Hidden');
      }, { timeout: 1000 });
      
      const endTime = Date.now();
      const elapsed = endTime - startTime;
      
      expect(elapsed).toBeLessThan(500);
    });
  });

  // ============================================================================
  // FR-11: Per-Participant visibility toggle from Director's Board
  // ============================================================================
  test.describe('FR-11: Per-Participant Toggle', () => {
    test('Clicking participant card toggles visibility', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const participantName = 'Player1';
      const card = page.locator(`.sp-participant-card:has-text("${participantName}")`);
      
      // Obtenir l'état initial
      const initialState = await card.locator('.sp-state-badge').textContent();
      
      // Cliquer pour toggler
      await card.click();
      await page.waitForTimeout(500);
      
      // Obtenir l'état après toggle
      const newState = await card.locator('.sp-state-badge').textContent();
      
      // Les états devraient être différents
      expect(initialState).not.toBe(newState);
    });

    test('Toggle via card works same as context menu', async ({ page }) => {
      const participantName = 'Player1';
      
      // Cacher via le board
      await toggleParticipantInBoard(page, participantName);
      
      const card = page.locator(`.sp-participant-card:has-text("${participantName}")`);
      const boardState = await card.locator('.sp-state-badge').textContent();
      
      // Vérifier via l'AV Tile
      const tile = page.locator(`.av-tile:has-text("${participantName}")`);
      const tileBadge = tile.locator('.sp-visibility-badge');
      const tileState = await tileBadge.textContent();
      
      // Les deux devraient montrer "Hidden"
      expect(boardState).toContain('Hidden');
      expect(tileState).toContain('Hidden');
    });

    test('Pending state shows pulse animation (reduced motion: static)', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Toggling rapidement devrait montrer l'état pending
      const card = page.locator('.sp-participant-card').first();
      await card.click();
      await card.click();
      
      // Vérifier l'animation de pulse
      const stateRing = card.locator('.sp-state-ring--pending');
      
      // En mode normal, devrait avoir l'animation
      // En mode reduced motion, devrait être statique
      const prefersReduced = await page.evaluate(() => {
        return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      });
      
      if (!prefersReduced) {
        await expect(stateRing).toBeVisible({ timeout: 2000 });
      }
    });
  });

  // ============================================================================
  // FR-12: Bulk Show All / Hide All with one-step Undo
  // ============================================================================
  test.describe('FR-12: Bulk Actions', () => {
    test('Show All button shows all participants', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // D'abord cacher tous les participants
      for (const player of TEST_PLAYERS) {
        await toggleParticipantInBoard(page, player);
      }
      
      // Cliquer sur Show All
      await clickFoundryButton(page, 'button:has-text("Show All")');
      
      // Vérifier que tous sont visibles
      for (const player of TEST_PLAYERS) {
        const card = page.locator(`.sp-participant-card:has-text("${player}")`);
        const state = await card.locator('.sp-state-badge').textContent();
        expect(state).toContain('Active');
      }
    });

    test('Hide All button hides all participants', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Cliquer sur Hide All
      await clickFoundryButton(page, 'button:has-text("Hide All")');
      
      // Vérifier que tous sont cachés
      const cards = page.locator('.sp-participant-card');
      const count = await cards.count();
      
      for (let i = 0; i < count; i++) {
        const card = cards.nth(i);
        const state = await card.locator('.sp-state-badge').textContent();
        expect(state).toContain('Hidden');
      }
    });

    test('Undo restores Visibility Matrix to previous state', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // État initial: tous visibles
      // Exécuter Hide All
      await clickFoundryButton(page, 'button:has-text("Hide All")');
      
      // Exécuter Undo
      await clickFoundryButton(page, 'button:has-text("Undo")');
      
      // Vérifier que tous sont revenus à l'état initial (visible)
      const cards = page.locator('.sp-participant-card');
      const count = await cards.count();
      
      for (let i = 0; i < count; i++) {
        const card = cards.nth(i);
        const state = await card.locator('.sp-state-badge').textContent();
        expect(state).toContain('Active');
      }
    });

    test('Second undo is unavailable after first (no-op)', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Exécuter Hide All
      await clickFoundryButton(page, 'button:has-text("Hide All")');
      
      // Premier Undo
      await clickFoundryButton(page, 'button:has-text("Undo")');
      
      // Vérifier que le bouton Undo est désactivé
      const undoButton = page.locator('button:has-text("Undo"):disabled');
      await expect(undoButton).toBeVisible({ timeout: 5000 });
    });
  });

  // ============================================================================
  // FR-13: Spotlight action
  // ============================================================================
  test.describe('FR-13: Spotlight Action', () => {
    test('Spotlight shows exactly one participant and hides all others', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const spotlightTarget = 'Player1';
      const card = page.locator(`.sp-participant-card:has-text("${spotlightTarget}")`);
      
      // Cliquer avec majuscule pour spotlight (ou utiliser le bouton dédié)
      await card.hover();
      await page.keyboard.press('s'); // ou utiliser le bouton contextuel
      
      // Vérifier que seul Player1 est visible
      const cards = page.locator('.sp-participant-card');
      const count = await cards.count();
      
      for (let i = 0; i < count; i++) {
        const c = cards.nth(i);
        const name = await c.locator('.sp-participant-name').textContent();
        const state = await c.locator('.sp-state-badge').textContent();
        
        if (name.includes(spotlightTarget)) {
          expect(state).toContain('Active');
        } else {
          expect(state).toContain('Hidden');
        }
      }
    });

    test('Restore action reverts to pre-spotlight snapshot', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // État initial: Player1 caché, Player2 visible
      await toggleParticipantInBoard(page, 'Player1');
      
      // Spotlight Player2
      const card = page.locator(`.sp-participant-card:has-text("Player2")`);
      await card.hover();
      await page.keyboard.press('s');
      
      // Restore
      await clickFoundryButton(page, 'button:has-text("Restore")');
      
      // Vérifier que Player1 est toujours caché, Player2 visible
      const card1 = page.locator(`.sp-participant-card:has-text("Player1")`);
      const card2 = page.locator(`.sp-participant-card:has-text("Player2")`);
      
      expect(await card1.locator('.sp-state-badge').textContent()).toContain('Hidden');
      expect(await card2.locator('.sp-state-badge').textContent()).toContain('Active');
    });
  });

  // ============================================================================
  // FR-14: Keyboard Shortcuts
  // ============================================================================
  test.describe('FR-14: Keyboard Accessibility', () => {
    test('Ctrl+Shift+V opens/closes Director\'s Board', async ({ page }) => {
      // Fermer si déjà ouvert
      const isOpen = await page.locator('.scrying-pool-directors-board').count();
      if (isOpen > 0) {
        await page.keyboard.press('Escape');
        await page.waitForTimeout(500);
      }
      
      // Ouvrir avec le raccourci
      await page.keyboard.press('Control+Shift+V');
      await page.waitForSelector('.scrying-pool-directors-board', {
        state: 'visible',
        timeout: 5000,
      });
      
      // Fermer avec Escape
      await page.keyboard.press('Escape');
      await page.waitForSelector('.scrying-pool-directors-board', {
        state: 'hidden',
        timeout: 5000,
      });
    });

    test('Arrow keys move focus between participant cards', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const firstCard = page.locator('.sp-participant-card').first();
      await firstCard.focus();
      
      // Appuyer sur la flèche droite
      await page.keyboard.press('ArrowRight');
      
      // Le focus devrait être sur la carte suivante
      const activeElement = await page.evaluate(() => document.activeElement);
      expect(activeElement.classList.contains('sp-participant-card')).toBeTruthy();
    });

    test('Space/Enter toggles focused participant visibility', async ({ page }) => {
      await openDirectorsBoard(page);
      
      const firstCard = page.locator('.sp-participant-card').first();
      await firstCard.focus();
      
      const initialState = await firstCard.locator('.sp-state-badge').textContent();
      
      // Appuyer sur Espace
      await page.keyboard.press('Space');
      
      const newState = await firstCard.locator('.sp-state-badge').textContent();
      expect(initialState).not.toBe(newState);
    });

    test('Ctrl+Shift+S triggers Show All', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Cacher tous
      await clickFoundryButton(page, 'button:has-text("Hide All")');
      
      // Appuyer sur Ctrl+Shift+S
      await page.keyboard.press('Control+Shift+S');
      
      // Vérifier que tous sont visibles
      const cards = page.locator('.sp-participant-card');
      const count = await cards.count();
      
      for (let i = 0; i < count; i++) {
        const card = cards.nth(i);
        const state = await card.locator('.sp-state-badge').textContent();
        expect(state).toContain('Active');
      }
    });

    test('Ctrl+Shift+H triggers Hide All', async ({ page }) => {
      await openDirectorsBoard(page);
      
      // Appuyer sur Ctrl+Shift+H
      await page.keyboard.press('Control+Shift+H');
      
      // Vérifier que tous sont cachés
      const cards = page.locator('.sp-participant-card');
      const count = await cards.count();
      
      for (let i = 0; i < count; i++) {
        const card = cards.nth(i);
        const state = await card.locator('.sp-state-badge').textContent();
        expect(state).toContain('Hidden');
      }
    });
  });

  // ============================================================================
  // FR-20: Toast Notifications
  // ============================================================================
  test.describe('FR-20: Toast Notifications', () => {
    test('GM visibility change generates toast notification', async ({ page }) => {
      const participantName = 'Player1';
      
      // Changer la visibilité
      await hideParticipant(page, participantName);
      
      // Attendre la notification
      await waitForNotification(page, `GM hid ${participantName}'s camera`, 5000);
    });

    test('Show action generates toast notification', async ({ page }) => {
      const participantName = 'Player1';
      
      // D'abord cacher
      await hideParticipant(page, participantName);
      
      // Puis montrer
      await showParticipant(page, participantName);
      
      // Attendre la notification
      await waitForNotification(page, `GM showed ${participantName}'s camera`, 5000);
    });

    test('Notification uses participant display name', async ({ page }) => {
      const participantName = 'Player1';
      
      await hideParticipant(page, participantName);
      
      // Capturer le texte exact de la notification
      const notification = page.locator('.notification');
      const text = await notification.textContent();
      
      expect(text).toContain(participantName);
      expect(text).toContain('hid');
    });

    test('Notification uses FoundryVTT native notification UI', async ({ page }) => {
      const participantName = 'Player1';
      
      await hideParticipant(page, participantName);
      
      // Vérifier que la notification a la classe Foundry
      const notification = page.locator('.notification.toast');
      await expect(notification).toBeVisible({ timeout: 5000 });
    });
  });

  // ============================================================================
  // FR-21: Notification Verbosity
  // ============================================================================
  test.describe('FR-21: Notification Verbosity Configuration', () => {
    test('Notification verbosity can be set to All', async ({ page }) => {
      // Ouvrir les paramètres du module
      await page.goto('https://localhost:31000', { waitUntil: 'domcontentloaded' });
      await waitForFoundryReady(page);
      
      // Naviguer vers les paramètres
      await page.locator('button[aria-label="Configure Settings"]').click();
      await page.waitForSelector('.app-v2.settings', { state: 'visible' });
      
      // Sélectionner Scrying Pool
      await page.locator('button:has-text("Scrying Pool")').click();
      
      // Trouver le paramètre de verbosité
      await page.waitForSelector('.sp-notification-verbosity-select', { timeout: 5000 });
      await page.locator('.sp-notification-verbosity-select').selectOption('All');
      
      // Sauvegarder
      await page.locator('button:has-text("Save")').click();
    });

    test('Notification verbosity can be set to GM Only', async ({ page }) => {
      // Similaire au test précédent mais avec "GM Only"
      await page.goto('https://localhost:31000', { waitUntil: 'domcontentloaded' });
      await waitForFoundryReady(page);
      
      await page.locator('button[aria-label="Configure Settings"]').click();
      await page.waitForSelector('.app-v2.settings', { state: 'visible' });
      
      await page.locator('button:has-text("Scrying Pool")').click();
      
      await page.waitForSelector('.sp-notification-verbosity-select', { timeout: 5000 });
      await page.locator('.sp-notification-verbosity-select').selectOption('GM Only');
      
      await page.locator('button:has-text("Save")').click();
    });

    test('Notification verbosity can be set to Silent', async ({ page }) => {
      await page.goto('https://localhost:31000', { waitUntil: 'domcontentloaded' });
      await waitForFoundryReady(page);
      
      await page.locator('button[aria-label="Configure Settings"]').click();
      await page.waitForSelector('.app-v2.settings', { state: 'visible' });
      
      await page.locator('button:has-text("Scrying Pool")').click();
      
      await page.waitForSelector('.sp-notification-verbosity-select', { timeout: 5000 });
      await page.locator('.sp-notification-verbosity-select').selectOption('Silent');
      
      await page.locator('button:has-text("Save")').click();
    });
  });
});
