/**
 * Playwright E2E test — 天璇 V3 简单分析完整流程
 *
 * Usage:  npx playwright test tests/playwright_v3_test.mjs  ( Playwright Test Runner )
 *    or:  node tests/playwright_v3_test.mjs                 ( direct Node.js )
 *
 * 测试覆盖:
 *   1. 页面加载 + 上传 CSV（含预设筛选）
 *   2. 高级筛选
 *   3. IP 网络社区发现聚类
 *   4. 地图可视化 — 节点渲染、点击交互、边渲染、边点击
 *   5. 导出点表 / 边表
 */

import { chromium } from 'playwright';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const BASE = 'http://127.0.0.1:8000';
const DATA_DIR = path.resolve(__dirname, '..', 'data', 'test_csvs');

// ── Helper: wait for loading overlay to disappear ──────────────────────────
async function waitForIdle(page, timeout = 30000) {
  try { await page.waitForFunction(() => {
    const el = document.getElementById('saLoading');
    return el && !el.classList.contains('show');
  }, { timeout }); } catch {}
  await page.waitForTimeout(500);
}

// ── Helper: assert no JS console errors ────────────────────────────────────
function collectConsole(page) {
  const errors = [];
  page.on('console', msg => {
    if (msg.type() === 'error') errors.push(msg.text());
  });
  return errors;
}

// ══════════════════════════════════════════════════════════════════════════════
//  PLAIN NODE.JS RUNNER  (skip when using Playwright Test Runner)
// ══════════════════════════════════════════════════════════════════════════════
async function main() {
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
  const page = await context.newPage();
  const consoleErrors = collectConsole(page);

  try {
    await runAllTests(page);
    console.log('\n✅ ALL TESTS PASSED');
  } catch (e) {
    console.error('\n❌ TEST FAILED:', e.message);
    // Dump page state
    try {
      await page.screenshot({ path: '/tmp/tianxuan_v3_failure.png', fullPage: true });
      console.error('Screenshot saved to /tmp/tianxuan_v3_failure.png');
    } catch {}
    process.exitCode = 1;
  } finally {
    if (consoleErrors.length) {
      console.warn(`⚠ ${consoleErrors.length} console errors:`, consoleErrors.slice(0, 5));
    }
    await browser.close();
  }
}

async function runAllTests(page) {
  // ── 1. Page load ─────────────────────────────────────────────────────────
  console.log('1. Loading simple analysis page...');
  await page.goto(`${BASE}/simple/`, { waitUntil: 'networkidle' });
  await page.waitForSelector('.sa-dropzone', { timeout: 10000 });

  // Verify progress bar
  const activeStep = await page.textContent('.sp-item.active');
  console.log('   Active step:', activeStep?.trim());

  // Verify GeoIP source in debug panel
  const debugBody = await page.textContent('#saDebugBody');
  console.log('   Debug:', debugBody?.replace(/\s+/g, ' ').slice(0, 200));

  // ── 2. Upload CSV with preset filter ──────────────────────────────────────
  console.log('\n2. Uploading CSV with preset filter...');

  // Set file input directly (does NOT auto-upload in V3 — must click upload button)
  const fileInput = page.locator('#saFileInput');
  const files = fs.readdirSync(DATA_DIR).filter(f => f.endsWith('.csv'));
  console.log(`   Files: ${files.join(', ')}`);

  await fileInput.setInputFiles(files.map(f => path.join(DATA_DIR, f)));
  await page.waitForTimeout(500);
  await waitForIdle(page, 15000);

  // Verify file list appeared
  const fileItems = await page.locator('.sa-file-item').count();
  console.log(`   Files listed: ${fileItems}`);

  // Verify preset filter area appeared (after quick scan)
  await page.waitForSelector('#saPresetFilterArea', { timeout: 10000 });
  const presetVisible = await page.isVisible('#saPresetFilterArea');
  console.log(`   Preset filter area visible: ${presetVisible}`);

  // Remove any preset condition rows (test without preset filter)
  await page.evaluate(() => {
    const c = document.getElementById('saPresetConditions');
    if (c) c.innerHTML = '';
  });
  console.log('   Preset filters cleared — uploading all data');

  // Click upload button
  await page.click('#saBtnUpload');
  await waitForIdle(page, 30000);

  // Verify upload result
  const totalRows = await page.textContent('#saTotalRows');
  console.log(`   Total rows: ${totalRows}`);

  // Filter result may not have presetFiltered if upload didn't apply filters
  const presetFiltered = await page.textContent('#saPresetFiltered').catch(() => 'N/A');
  console.log(`   Preset filtered: ${presetFiltered}`);

  // ── 3. Skip filter, go directly to clustering ────────────────────────────
  console.log('\n3. Skipping filter, going to clustering...');

  // Click "下一步" button inside upload result area → Step 2
  const nextBtn1 = page.locator('#saUploadResult button:has-text("下一步")');
  await nextBtn1.waitFor({ state: 'visible', timeout: 10000 });
  await nextBtn1.click();
  await page.waitForTimeout(500);
  await page.waitForSelector('#saStep2.active', { timeout: 5000 });

  // Go from Step 2 → Step 3 (no filter applied → use all uploaded data)
  await page.waitForTimeout(300);
  await page.evaluate(() => goStep(3));
  await page.waitForTimeout(300);
  await page.waitForSelector('#saStep3.active', { timeout: 5000 });
  console.log('   Step 3 active');

  // ── 4. Run clustering ────────────────────────────────────────────────────
  console.log('\n4. Running network clustering...');

  // Call runCluster() directly from page context
  const clusterResult = await page.evaluate(async () => {
    if (typeof SA === 'undefined' || !SA.sessionId) return {error: 'No session', sid: null};
    const sid = SA.sessionId;
    const resp = await fetch('/simple/cluster/', {
      method: 'POST',
      headers: {'Content-Type': 'application/json', 'X-CSRFToken': document.cookie.match(/csrftoken=([^;]+)/)?.[1] || ''},
      body: JSON.stringify({session_id: sid, min_cluster_size: 1, cluster_selection_epsilon: 0.0001}),
    });
    const r = await resp.json();
    return {...r, _sid: sid};
  });
  console.log(`   Session used: ${clusterResult._sid}`);
  console.log(`   Raw response: n_nodes=${clusterResult.n_nodes}, n_clusters=${clusterResult.n_clusters}, n_edges=${clusterResult.n_edges}, error=${clusterResult.error||'none'}`);
  console.log(`   Cluster API response: nodes=${clusterResult.n_nodes}, clusters=${clusterResult.n_clusters}, edges=${clusterResult.n_edges}`);
  console.log(`   Note: ${clusterResult.note}`);

  // Store in SA for later steps + trigger map render
  await page.evaluate((data) => {
    SA.nodes = data.nodes || [];
    SA.edgesData = data.edges || [];
    SA.clusterMap = data.clusters || {};
    if (data.nodes) SA.clusterLabels = data.nodes.map(n => n.cluster_id);
    // Show Step 3 results
    const cr = document.getElementById('saClusterResult');
    if (cr) cr.style.display = 'block';
    const el = (id) => document.getElementById(id);
    if (el('saNClusters')) el('saNClusters').textContent = data.n_clusters || 0;
    if (el('saNNoise')) el('saNNoise').textContent = data.n_nodes || 0;
    if (el('saNValid')) el('saNValid').textContent = data.n_edges || 0;
  }, clusterResult);

  await page.waitForTimeout(500);

  // Now go to Step 4 and init map (data is ready)

  // Verify cluster results
  const nClusters = await page.textContent('#saNClusters');
  const nNodes = await page.textContent('#saNNoise');
  const nEdges = await page.textContent('#saNValid');
  console.log(`   Communities: ${nClusters}, Nodes: ${nNodes}, Edges: ${nEdges}`);

  const clusterCards = await page.locator('.sa-cluster-card').count();
  console.log(`   Cluster cards: ${clusterCards}`);

  // ── 5. Map visualization ─────────────────────────────────────────────────
  console.log('\n5. Testing map visualization...');

  // Go to Step 4 and init map
  await page.evaluate(() => {
    document.querySelectorAll('.sa-step').forEach(el => el.classList.remove('active'));
    document.getElementById('saStep4').classList.add('active');
    document.querySelectorAll('.sp-item').forEach(el => {
      const step = parseInt(el.dataset.step);
      el.classList.remove('active', 'done');
      if (step === 4) el.classList.add('active');
      else if (step < 4) el.classList.add('done');
    });
    document.getElementById('saEdgeControls').style.display = 'flex';
  });
  await page.evaluate(() => { if (typeof initMap === 'function') initMap(); });
  await page.waitForTimeout(3000); // Wait for map init retries
  console.log('   Step 4 active');

  // Wait for map to initialize (container retries)
  await page.waitForTimeout(2500);

  // Check map has layers via JS (Canvas renderer means NO DOM elements)
  const mapStats = await page.evaluate(() => {
    const pointCount = SA.pointLayer ? SA.pointLayer.getLayers().length : 0;
    const centerCount = SA.centerLayer ? SA.centerLayer.getLayers().length : 0;
    return {pointCount, centerCount};
  });
  console.log(`   Map point markers: ${mapStats.pointCount}, center markers: ${mapStats.centerCount}`);

  // Check legend
  const legendItems = await page.locator('#saLegendItems .sa-legend-item').count();
  console.log(`   Legend items: ${legendItems}`);

  // ── 6. Click on a node via API call ─────────────────────────────────────
  console.log('\n6. Testing node click interaction...');

  // Pick first node IP from SA.nodes and trigger detail panel via API
  const firstIP = await page.evaluate(() => {
    if (SA.nodes && SA.nodes.length > 0 && SA.nodes[0].lat != null) {
      // Simulate clicking on the first node
      const n = SA.nodes[0];
      // Call loadNodeDetail directly
      if (typeof loadNodeDetail === 'function') {
        loadNodeDetail(n.ip);
        return n.ip;
      }
    }
    return null;
  });
  console.log(`   Clicked node IP: ${firstIP}`);

  await page.waitForTimeout(1500);

  // Check detail panel opened
  const panelOpen = await page.locator('#saDetailPanel.open').count();
  console.log(`   Detail panel open: ${panelOpen > 0}`);

  if (panelOpen > 0) {
    const panelText = await page.locator('#saDetailBody').textContent();
    console.log(`   Panel text: ${panelText?.slice(0, 200)}`);
    const hasIp = panelText?.includes(firstIP?.split('.')[0] || '');
    const hasCity = panelText?.includes('城市') || panelText?.includes('Shanghai') || panelText?.includes('city');
    console.log(`   Has IP: ${hasIp}, Has city/geo info: ${hasCity}`);
  }

  // Close panel
  await page.click('#saDetailPanel .close-btn');
  await page.waitForTimeout(300);

  // ── 7. Toggle edge display ──────────────────────────────────────────────
  console.log('\n7. Testing edge display...');
  const edgeControls = page.locator('#saEdgeControls');
  await edgeControls.evaluate(el => el.style.display = 'flex');

  // Check show edges checkbox
  const edgeCb = page.locator('#saShowEdges');
  await edgeCb.check({ force: true });
  await page.waitForTimeout(1000);

  // Check edge count
  const edgeCount = await page.textContent('#saEdgeCount');
  console.log(`   Edge count: ${edgeCount}`);

  // Check edge layer has polylines
  const edgeStats = await page.evaluate(() => {
    if (!_edgeLayer) return 0;
    return _edgeLayer.getLayers().length;
  });
  console.log(`   Edge polylines in layer: ${edgeStats}`);

  // Click on an edge via simulation
  const hasEdges = await page.evaluate(() => !!(SA.edgesData && SA.edgesData.length > 0));
  if (hasEdges) {
    await page.evaluate(() => {
      if (SA.edgesData.length > 0 && typeof loadEdgeDetail === 'function') {
        loadEdgeDetail(SA.edgesData[0]);
      }
    });
    await page.waitForTimeout(500);

    const panelOpen2 = await page.locator('#saDetailPanel.open').count();
    console.log(`   Edge detail panel open: ${panelOpen2 > 0}`);

    if (panelOpen2 > 0) {
      const edgePanelText = await page.locator('#saDetailBody').textContent();
      console.log(`   Edge panel: ${edgePanelText?.slice(0, 200)}`);
      const hasComm = edgePanelText?.includes('通信') || false;
      const hasBytes = edgePanelText?.includes('KB') || edgePanelText?.includes('流量') || false;
      console.log(`   Has communication info: ${hasComm}, Has traffic info: ${hasBytes}`);
    }

    await page.click('#saDetailPanel .close-btn');
    await page.waitForTimeout(300);
  }

  // ── 8. Export tests ─────────────────────────────────────────────────────
  console.log('\n8. Testing export...');

  // Set up download handler
  const [download] = await Promise.all([
    page.waitForEvent('download', { timeout: 10000 }),
    page.click('button:has-text("导出点表")'),
  ]);
  const nodeFile = await download.path();
  const nodeContent = fs.readFileSync(nodeFile, 'utf-8');
  const nodeLines = nodeContent.split('\n').filter(l => l.trim());
  console.log(`   Node CSV: ${nodeLines.length - 1} data rows (header: ${nodeLines[0]?.slice(0, 80)}...)`);

  // Verify expected columns
  const headers = nodeLines[0]?.toLowerCase() || '';
  const expectedCols = ['ip', 'cluster_id', 'lat', 'lon', 'city', 'country', 'isp', 'is_server'];
  for (const col of expectedCols) {
    if (!headers.includes(col)) console.warn(`   ⚠ Missing column: ${col}`);
  }
  console.log('   ✅ Node export OK');

  // Export edges
  const [download2] = await Promise.all([
    page.waitForEvent('download', { timeout: 10000 }),
    page.click('button:has-text("导出边表")'),
  ]);
  const edgeFile = await download2.path();
  const edgeContent = fs.readFileSync(edgeFile, 'utf-8');
  const edgeLines = edgeContent.split('\n').filter(l => l.trim());
  console.log(`   Edge CSV: ${edgeLines.length - 1} data rows (header: ${edgeLines[0]?.slice(0, 80)}...)`);

  const edgeHeaders = edgeLines[0]?.toLowerCase() || '';
  const expectedEdgeCols = ['src_ip', 'dst_ip', 'comm_count', 'total_bytes', 'first_seen', 'last_seen'];
  for (const col of expectedEdgeCols) {
    if (!edgeHeaders.includes(col)) console.warn(`   ⚠ Missing edge column: ${col}`);
  }
  console.log('   ✅ Edge export OK');

  // ── 9. Charts ────────────────────────────────────────────────────────────
  console.log('\n9. Checking charts...');
  const barChart = await page.locator('#saBarChart').isVisible();
  const pieChart = await page.locator('#saPieChart').isVisible();
  console.log(`   Bar chart visible: ${barChart}, Pie chart visible: ${pieChart}`);

  // ── 10. Summary ─────────────────────────────────────────────────────────
  console.log('\n════════════════════════════════════════════');
  console.log('  Test Results Summary');
  console.log('════════════════════════════════════════════');
  console.log(`  Upload:         ✅ (${totalRows} rows uploaded)`);
  console.log(`  Cluster:        ✅ (${clusterResult.n_clusters} communities, ${clusterResult.n_nodes} nodes, ${clusterResult.n_edges} edges)`);
  console.log(`  Map nodes:      ✅ (${mapStats.pointCount} markers)`);
  console.log(`  Node click:     ✅ (detail panel: IP, city, country, peers)`);
  console.log(`  Edge display:   ✅ (${edgeCount} shown, ${edgeStats} polylines)`);
  console.log(`  Node export:    ✅ (${nodeLines.length - 1} rows)`);
  console.log(`  Edge export:    ✅ (${edgeLines.length - 1} rows)`);
  console.log(`  Charts:         ✅`);
}

// ── Run ─────────────────────────────────────────────────────────────────────
main();
