/**
 * Epic 3: Scene-Aware Camera Automation (Scene Presets) - Tests E2E
 * 
 * FR-15: GM can save named Scene Preset from current Visibility Matrix
 * FR-16: GM can load Scene Preset at any time
 * FR-17: Scene Preset auto-applies on Scene activation
 * FR-18: Disable auto-apply per-scene or globally
 * FR-19: Preset import/export as JSON
 */

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

const TEST_PRESET_NAME = 'TestPreset';
const TEST_PRESET_NAME_2 = 'TestPreset2';

test.describe('Epic 3: Scene-Aware Camera Automation', () => {
  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');
    }
    
    // Nettoyer les presets de test
    await cleanupTestPresets(page);
  });

  test.afterEach(async ({ page }) => {
    // Nettoyer après chaque test
    await cleanupTestPresets(page);
  });
});

/**
 * Nettoie les presets de test
 */
async function cleanupTestPresets(page) {
  await openDirectorsBoard(page);
  
  // Liste des presets à supprimer
  const presetsToDelete = [TEST_PRESET_NAME, TEST_PRESET_NAME_2];
  
  for (const presetName of presetsToDelete) {
    try {
      // Ouvrir la liste des presets
      await clickFoundryButton(page, 'button:has-text("Load Preset")');
      await page.waitForSelector('.sp-preset-list', { state: 'visible', timeout: 3000 });
      
      // Trouver et supprimer le preset
      const presetItem = page.locator(`.sp-preset-item:has-text("${presetName}")`);
      if (await presetItem.count() > 0) {
        await presetItem.hover();
        await clickFoundryButton(page, '.sp-preset-delete-button');
        await clickFoundryButton(page, 'button:has-text("Confirm")');
        await page.waitForTimeout(500);
      }
    } catch (error) {
      console.warn(`Could not delete preset ${presetName}:`, error.message);
    }
  }
}

// ============================================================================
// FR-15: Save named Scene Preset
// ============================================================================
test.describe('FR-15: Save Scene Preset', () => {
  test('GM can save current Visibility Matrix as named preset', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder le preset
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Vérifier la notification
    await waitForNotification(page, `Saved preset: ${TEST_PRESET_NAME}`, 5000);
  });

  test('Preset name must be unique per world', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder le premier preset
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Essayer de sauvegarder avec le même nom
    await page.locator('button:has-text("Save Preset")').click();
    await page.waitForSelector('.sp-save-preset-dialog', { state: 'visible' });
    await page.locator('.sp-save-preset-dialog input[name="presetName"]').fill(TEST_PRESET_NAME);
    
    // Devrait montrer une erreur ou ne pas permettre le doublon
    // Selon l'implémentation, soit:
    // 1. Le bouton Save est désactivé
    // 2. Une erreur apparaît
    const saveButton = page.locator('.sp-save-preset-dialog button:has-text("Save"):disabled');
    
    if (await saveButton.count() > 0) {
      await expect(saveButton).toBeVisible({ timeout: 3000 });
    } else {
      // Sinon, vérifier qu'une erreur apparaît
      const error = page.locator('.sp-error-message');
      await expect(error).toBeVisible({ timeout: 3000 });
    }
  });

  test('Preset captures full current Visibility Matrix', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Configurer un état spécifique : cacher Player1, montrer Player2
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    await page.waitForTimeout(500);
    
    // Sauvegarder
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Vérifier que le preset contient la matrice
    const presetData = await page.evaluate((presetName) => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        const preset = module.api.scenePresetManager.getPreset(presetName);
        return preset?.matrix;
      }
      return null;
    }, TEST_PRESET_NAME);
    
    expect(presetData).toBeTruthy();
    expect(presetData).toHaveProperty('_version');
    expect(presetData).toHaveProperty('matrix');
  });

  test('Preset names are editable', async ({ page }) => {
    await openDirectorsBoard(page);
    
    await clickFoundryButton(page, 'button:has-text("Save Preset")');
    await page.waitForSelector('.sp-save-preset-dialog', { state: 'visible' });
    
    const input = page.locator('.sp-save-preset-dialog input[name="presetName"]');
    await input.fill(TEST_PRESET_NAME);
    await expect(input).toHaveValue(TEST_PRESET_NAME);
    
    // Modifier le nom
    await input.fill(TEST_PRESET_NAME_2);
    await expect(input).toHaveValue(TEST_PRESET_NAME_2);
  });

  test('Up to 50 presets per world can be saved', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder plusieurs presets
    const presetCount = 10;
    for (let i = 0; i < presetCount; i++) {
      const name = `Preset${i}`;
      await page.locator('button:has-text("Save Preset")').click();
      await page.waitForSelector('.sp-save-preset-dialog', { state: 'visible' });
      await page.locator('.sp-save-preset-dialog input[name="presetName"]').fill(name);
      await clickFoundryButton(page, '.sp-save-preset-dialog button:has-text("Save")');
      await waitForNotification(page, `Saved preset: ${name}`, 3000);
    }
    
    // Vérifier que tous les presets sont dans la liste
    await clickFoundryButton(page, 'button:has-text("Load Preset")');
    await page.waitForSelector('.sp-preset-list', { state: 'visible' });
    
    const presetItems = page.locator('.sp-preset-item');
    expect(await presetItems.count()).toBeGreaterThanOrEqual(presetCount);
    
    // Nettoyer
    await cleanupTestPresets(page);
  });
});

// ============================================================================
// FR-16: Load Scene Preset
// ============================================================================
test.describe('FR-16: Load Scene Preset', () => {
  test('GM can load a preset at any time', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder un preset
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Changer manuellement l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    await page.waitForTimeout(500);
    
    // Charger le preset
    await loadScenePreset(page, TEST_PRESET_NAME);
    
    // Vérifier que l'état est revenu à celui du preset
    const card = page.locator('.sp-participant-card:has-text("Player1")');
    const state = await card.locator('.sp-state-badge').textContent();
    
    // Le preset sauvé avait Player1 visible (par défaut)
    expect(state).toContain('Active');
  });

  test('Loading preset overrides current Visibility Matrix', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Cacher tous les participants
    await clickFoundryButton(page, 'button:has-text("Hide All")');
    
    // Sauvegarder cet état
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Montrer tous
    await clickFoundryButton(page, 'button:has-text("Show All")');
    
    // Charger le preset "tous cachés"
    await loadScenePreset(page, TEST_PRESET_NAME);
    
    // 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('All clients receive state within 500ms', async ({ page }) => {
    // Sauvegarder un preset
    await openDirectorsBoard(page);
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Changer l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    await page.waitForTimeout(500);
    
    const startTime = Date.now();
    
    // Charger le preset
    await loadScenePreset(page, TEST_PRESET_NAME);
    
    const endTime = Date.now();
    const elapsed = endTime - startTime;
    
    // Vérifier que le changement s'est propagé rapidement
    const card = page.locator('.sp-participant-card:has-text("Player1")');
    const state = await card.locator('.sp-state-badge').textContent();
    expect(state).toContain('Active');
    
    // Note: En mode single-player test, on ne peut pas vérifier multi-clients
    // Mais le temps devrait être < 500ms
    expect(elapsed).toBeLessThan(500);
  });

  test('Loading generates notification "GM applied preset: [Preset Name]"', async ({ page }) => {
    await openDirectorsBoard(page);
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Changer l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    
    // Charger le preset
    await loadScenePreset(page, TEST_PRESET_NAME);
    
    // Vérifier la notification
    await waitForNotification(page, `GM applied preset: ${TEST_PRESET_NAME}`, 5000);
  });
});

// ============================================================================
// FR-17: Scene Preset Auto-Apply
// ============================================================================
test.describe('FR-17: Scene Auto-Apply', () => {
  test('Scene Preset auto-applies on Scene activation', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder un preset
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Associer le preset à la scène actuelle
    await page.evaluate((presetName) => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        const currentScene = game.scenes?.active;
        if (currentScene) {
          module.api.scenePresetManager.setScenePreset(
            currentScene.id,
            presetName
          );
        }
      }
    }, TEST_PRESET_NAME);
    
    // Changer manuellement l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    await page.waitForTimeout(500);
    
    // Recharger la scène (simuler le changement de scène)
    await page.evaluate(() => {
      const currentScene = game.scenes?.active;
      if (currentScene) {
        currentScene.activate();
      }
    });
    
    // Attendre l'auto-apply
    await page.waitForTimeout(1000);
    
    // Vérifier que l'état est revenu à celui du preset
    const card = page.locator('.sp-participant-card:has-text("Player1")');
    const state = await card.locator('.sp-state-badge').textContent();
    expect(state).toContain('Active');
  });

  test('Auto-apply has configurable pre-delay', async ({ page }) => {
    // Ce test vérifie que le délai est configurable
    // La valeur par défaut est 0ms
    const delay = await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        return module.api.scenePresetManager.autoApplyDelay;
      }
      return 0;
    });
    
    // Devrait être un nombre (ms)
    expect(typeof delay).toBe('number');
    expect(delay).toBeGreaterThanOrEqual(0);
    expect(delay).toBeLessThanOrEqual(5000);
  });

  test('All clients receive "Scene changed: camera layout updated" notification', async ({ page }) => {
    // Sauvegarder et associer un preset
    await openDirectorsBoard(page);
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    await page.evaluate((presetName) => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        const currentScene = game.scenes?.active;
        if (currentScene) {
          module.api.scenePresetManager.setScenePreset(
            currentScene.id,
            presetName
          );
        }
      }
    }, TEST_PRESET_NAME);
    
    // Changer manuellement l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    
    // Recharger la scène
    await page.evaluate(() => {
      const currentScene = game.scenes?.active;
      if (currentScene) {
        currentScene.activate();
      }
    });
    
    // Attendre la notification
    // Note: En mode single-player, la notification pourrait ne pas apparaître
    // car c'est le même utilisateur
    try {
      await waitForNotification(page, 'Scene changed: camera layout updated', 5000);
    } catch (error) {
      console.warn('Notification may not appear in single-player mode:', error.message);
    }
  });
});

// ============================================================================
// FR-18: Disable Auto-Apply
// ============================================================================
test.describe('FR-18: Disable Auto-Apply', () => {
  test('Auto-apply can be disabled per-scene', async ({ page }) => {
    await openDirectorsBoard(page);
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Désactiver l'auto-apply pour la scène actuelle
    await page.evaluate((presetName) => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        const currentScene = game.scenes?.active;
        if (currentScene) {
          module.api.scenePresetManager.setScenePreset(
            currentScene.id,
            presetName,
            { autoApply: false }
          );
        }
      }
    }, TEST_PRESET_NAME);
    
    // Changer manuellement l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    await page.waitForTimeout(500);
    
    // Recharger la scène
    await page.evaluate(() => {
      const currentScene = game.scenes?.active;
      if (currentScene) {
        currentScene.activate();
      }
    });
    
    // Attendre et vérifier que l'état N'A PAS changé
    await page.waitForTimeout(1500);
    
    const card = page.locator('.sp-participant-card:has-text("Player1")');
    const state = await card.locator('.sp-state-badge').textContent();
    
    // Devrait être toujours caché (car auto-apply est désactivé)
    expect(state).toContain('Hidden');
  });

  test('Auto-apply can be disabled globally', async ({ page }) => {
    // Désactiver l'auto-apply globalement
    await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        module.api.scenePresetManager.setAutoApplyEnabled(false);
      }
    });
    
    await openDirectorsBoard(page);
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    await page.evaluate((presetName) => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        const currentScene = game.scenes?.active;
        if (currentScene) {
          module.api.scenePresetManager.setScenePreset(
            currentScene.id,
            presetName,
            { autoApply: true } // Même si activé par scène, le global prend le dessus
          );
        }
      }
    }, TEST_PRESET_NAME);
    
    // Changer l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    await page.waitForTimeout(500);
    
    // Recharger la scène
    await page.evaluate(() => {
      const currentScene = game.scenes?.active;
      if (currentScene) {
        currentScene.activate();
      }
    });
    
    await page.waitForTimeout(1500);
    
    const card = page.locator('.sp-participant-card:has-text("Player1")');
    const state = await card.locator('.sp-state-badge').textContent();
    
    // Devrait être toujours caché (car auto-apply global est désactivé)
    expect(state).toContain('Hidden');
    
    // Réactiver l'auto-apply global
    await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        module.api.scenePresetManager.setAutoApplyEnabled(true);
      }
    });
  });

  test('Director\'s Board always provides manual override', async ({ page }) => {
    // Désactiver l'auto-apply
    await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        module.api.scenePresetManager.setAutoApplyEnabled(false);
      }
    });
    
    await openDirectorsBoard(page);
    
    // Sauvegarder un preset
    await saveScenePreset(page, TEST_PRESET_NAME);
    
    // Changer manuellement l'état
    await clickFoundryButton(page, '.sp-participant-card:has-text("Player1")');
    
    // Le bouton Load Preset devrait toujours être disponible
    const loadButton = page.locator('button:has-text("Load Preset")');
    await expect(loadButton).toBeEnabled({ timeout: 5000 });
    
    // Charger manuellement
    await loadScenePreset(page, TEST_PRESET_NAME);
    
    // Devrait fonctionner même avec auto-apply désactivé
    const card = page.locator('.sp-participant-card:has-text("Player1")');
    const state = await card.locator('.sp-state-badge').textContent();
    expect(state).toContain('Active');
    
    // Réactiver
    await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        module.api.scenePresetManager.setAutoApplyEnabled(true);
      }
    });
  });
});

// ============================================================================
// FR-19: Preset Import/Export
// ============================================================================
test.describe('FR-19: Preset Import/Export', () => {
  test('Preset export downloads all presets as JSON', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder quelques presets
    await saveScenePreset(page, 'ExportTest1');
    await saveScenePreset(page, 'ExportTest2');
    
    // Ouvrir le menu d'export
    await clickFoundryButton(page, 'button:has-text("Export")');
    await page.waitForSelector('.sp-export-dialog', { state: 'visible' });
    
    // Cliquer sur Export
    const downloadPromise = page.waitForEvent('download');
    await clickFoundryButton(page, '.sp-export-dialog button:has-text("Export")');
    
    const download = await downloadPromise;
    
    // Vérifier que le fichier est JSON
    expect(download.url()).toMatch(/\.json$/i);
    
    // Vérifier que le nom contient les presets
    expect(download.url()).toContain('presets');
    
    // Nettoyer
    await cleanupTestPresets(page);
  });

  test('Exported JSON is human-readable', async ({ page }) => {
    await openDirectorsBoard(page);
    await saveScenePreset(page, 'ReadableTest');
    
    await clickFoundryButton(page, 'button:has-text("Export")');
    await page.waitForSelector('.sp-export-dialog', { state: 'visible' });
    
    // Capturer le contenu JSON
    const jsonContent = await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        return JSON.stringify(module.api.scenePresetManager.getAllPresets(), null, 2);
      }
      return null;
    });
    
    expect(jsonContent).toBeTruthy();
    
    // Vérifier que c'est du JSON valide
    const parsed = JSON.parse(jsonContent);
    expect(parsed).toBeTruthy();
    expect(parsed).toHaveProperty('ExportTest');
    
    // Nettoyer
    await cleanupTestPresets(page);
  });

  test('Preset import reads JSON and merges or replaces', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Sauvegarder un preset existant
    await saveScenePreset(page, 'ExistingPreset');
    
    // Créer un JSON d'import
    const importData = {
      NewPreset1: {
        _version: 1,
        matrix: { player1: 'hidden', player2: 'hidden' }
      },
      NewPreset2: {
        _version: 1,
        matrix: { player1: 'active', player2: 'active' }
      }
    };
    
    // Importer via l'API
    await page.evaluate((data) => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        module.api.scenePresetManager.importPresets(data, { merge: true });
      }
    }, importData);
    
    // Vérifier que les nouveaux presets existent
    const presetNames = await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        return Object.keys(module.api.scenePresetManager.getAllPresets());
      }
      return [];
    });
    
    expect(presetNames).toContain('ExistingPreset');
    expect(presetNames).toContain('NewPreset1');
    expect(presetNames).toContain('NewPreset2');
    
    // Nettoyer
    await cleanupTestPresets(page);
  });

  test('Invalid JSON shows error', async ({ page }) => {
    await openDirectorsBoard(page);
    
    // Essayer d'importer du JSON invalide
    const result = await page.evaluate(() => {
      const module = game.modules.get('scrying-pool');
      if (module && module.api?.scenePresetManager) {
        try {
          module.api.scenePresetManager.importPresets('invalid json', { merge: true });
          return { success: true };
        } catch (e) {
          return { success: false, error: e.message };
        }
      }
      return { success: false, error: 'Module not found' };
    });
    
    // Devrait échouer
    expect(result.success).toBe(false);
    expect(result.error).toBeTruthy();
  });
});
