/**
 * Workflow Verification: Analyze/Index.tsx
 * Session: 97b5fc09-3273-4a27-a1c2-4f141611fa25
 *
 * Golden path and sad-path exercise for:
 *   - resources/js/Pages/Analyze/Index.tsx
 *   - resources/js/Pages/Analyze/Index.test.tsx
 *
 * Scope derived from diff (no SUCCESS_CRITERIA / WORKFLOW_BLAST_RADIUS files present).
 *
 * === Workflows verified ===
 * A. Golden path (completed run with findings): MetricsOverview rendered, "Showing:"
 *    window caption visible, WinnersLosers area present — zero console errors.
 * B. Processing/pending state: skeleton placeholders, "Analysis queued" banner shown,
 *    no blank screen, no console errors.
 * C. Failed analysis: friendly error card + "Retry Analysis" button, no raw exception.
 * D. Low-data empty state (ran but below threshold): "significance threshold" message
 *    shown when completed run has overall metrics but zero findings.
 * E. Permission denied: unauthenticated access redirects to /login.
 *
 * === DB state management ===
 * Each test sets up and tears down its own DB state via PHP tinker helpers.
 * Tests run serially (describe.configure({ mode: 'serial' })) to prevent concurrent
 * DB mutations to the same analysis run.
 *
 * === public_id routing ===
 * Site model uses HasPublicId — URL uses ULID (public_id), not integer PK.
 * Resolved via artisan tinker before the suite.
 */

import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { expect, test, type E2ESeedData } from '../fixtures/auth';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Run tests in this file serially to avoid concurrent DB mutations to the same run
test.describe.configure({ mode: 'serial' });

// ---------------------------------------------------------------------------
// Known environmental noise — pre-existing, not introduced by this session
// ---------------------------------------------------------------------------
const KNOWN_NOISE = [
  '/api/lead-score/pql-signal',
  '/profile',
  'ERR_TOO_MANY_REDIRECTS',
  'favicon',
  'chrome-extension',
  'extension',
  'analytics',
  'pql-signal',
];

// ---------------------------------------------------------------------------
// PHP execution helper (avoids shell-quoting issues)
// ---------------------------------------------------------------------------
function runPhp(phpCode: string): string {
  const tmpFile = path.join('/tmp', `e2e_analyze_${process.pid}_${Date.now()}.php`);
  try {
    fs.writeFileSync(tmpFile, phpCode, 'utf8');
    return execSync(`php artisan tinker --execute="$(cat ${tmpFile})" 2>&1`, {
      cwd: process.cwd(),
      encoding: 'utf8',
      timeout: 30_000,
      shell: '/bin/bash',
    });
  } finally {
    try { fs.unlinkSync(tmpFile); } catch { /* ignore */ }
  }
}

function resolveSitePublicId(siteId: number): string {
  const raw = runPhp(
    `$s = \\App\\Models\\Site::find(${siteId}); echo $s ? $s->public_id : 'NOT_FOUND';`,
  );
  const result = raw.trim().split('\n').map((l) => l.trim()).filter(Boolean).pop() ?? '';
  if (!result || result === 'NOT_FOUND') {
    throw new Error(`resolveSitePublicId(${siteId}) failed. Raw:\n${raw}`);
  }
  return result;
}

/** Set analysis run summary with an `overall` block so MetricsOverview renders. */
function setAnalysisRunSummaryWithOverall(analysisRunId: number): void {
  runPhp(`
\\App\\Models\\AnalysisRun::where('id', ${analysisRunId})->update([
  'summary' => json_encode([
    'total_pages' => 1,
    'winners_count' => 0,
    'losers_count' => 1,
    'significant' => 1,
    'duration_ms' => 1200,
    'overall' => [
      'clicks_after' => 1200,
      'clicks_delta_pct' => -29.17,
      'impressions_after' => 50000,
      'impressions_delta_pct' => -10.5,
      'ctr_after' => 2.4,
      'ctr_delta_pct' => -5.2,
      'position_after' => 12.3,
      'position_delta_pct' => 3.1,
    ],
  ]),
]);
echo "ok";
  `);
}

/** Restore analysis run summary to seed state (no `overall` key). */
function clearAnalysisRunSummaryOverall(analysisRunId: number): void {
  runPhp(`
\\App\\Models\\AnalysisRun::where('id', ${analysisRunId})->update([
  'summary' => json_encode([
    'total_pages' => 1,
    'winners_count' => 0,
    'losers_count' => 1,
    'significant' => 1,
    'duration_ms' => 1200,
  ]),
]);
echo "ok";
  `);
}

/** Set analysis run to completed state. */
function setAnalysisRunCompleted(analysisRunId: number): void {
  runPhp(`
\\App\\Models\\AnalysisRun::where('id', ${analysisRunId})->update([
  'status' => 'completed',
  'error' => null,
  'started_at' => now()->subHour()->toDateTimeString(),
  'completed_at' => now()->toDateTimeString(),
]);
echo "ok";
  `);
}

/** Set analysis run to pending (queued) state. */
function setAnalysisRunPending(analysisRunId: number): void {
  runPhp(`
\\App\\Models\\AnalysisRun::where('id', ${analysisRunId})->update([
  'status' => 'pending',
  'error' => null,
  'started_at' => null,
  'completed_at' => null,
]);
echo "ok";
  `);
}

/** Set analysis run to failed state with a given error string. */
function setAnalysisRunFailed(analysisRunId: number, errorMessage: string): void {
  runPhp(`
\\App\\Models\\AnalysisRun::where('id', ${analysisRunId})->update([
  'status' => 'failed',
  'error' => '${errorMessage.replace(/'/g, "\\'")}',
  'completed_at' => now()->toDateTimeString(),
]);
echo "ok";
  `);
}

/** Temporarily soft-delete (or hide) all findings for a given run. */
function clearFindings(analysisRunId: number): void {
  runPhp(`
DB::table('findings')->where('analysis_run_id', ${analysisRunId})->delete();
echo "cleared";
  `);
}

/** Re-create the seeded finding for a given run and wp_post. */
function restoreFindings(analysisRunId: number, pageUrl: string): void {
  runPhp(`
$pageUrl = '${pageUrl}';
\\App\\Models\\Finding::create([
  'analysis_run_id' => ${analysisRunId},
  'page_url' => $pageUrl,
  'page_url_hash' => hash('sha256', $pageUrl),
  'type' => 'traffic_change',
  'direction' => 'negative',
  'metric_before' => 120,
  'metric_after' => 85,
  'delta_absolute' => -35,
  'delta_percent' => -29.17,
  'segment_type' => 'overall',
  'segment_value' => null,
  'supporting_data' => null,
]);
echo "restored";
  `);
}

// ---------------------------------------------------------------------------
// Suite-level state
// ---------------------------------------------------------------------------
let sitePublicId: string;
let seedData: E2ESeedData;
let seededPageUrl: string;

test.beforeAll(() => {
  const seedPath = path.join(__dirname, '..', '.seed-data.json');
  if (!fs.existsSync(seedPath)) {
    throw new Error('Seed data missing — run global setup first');
  }
  seedData = JSON.parse(fs.readFileSync(seedPath, 'utf8')) as E2ESeedData;
  sitePublicId = resolveSitePublicId(seedData.siteId);

  // Resolve the page URL used by the seeded WpPost finding
  const raw = runPhp(
    `$w = \\App\\Models\\WpPost::find(${seedData.wpPostId}); echo $w ? $w->url : 'NOT_FOUND';`,
  );
  const resolved = raw.trim().split('\n').map((l) => l.trim()).filter(Boolean).pop() ?? '';
  if (!resolved || resolved === 'NOT_FOUND') {
    throw new Error(`Could not resolve WpPost url for id=${seedData.wpPostId}. Raw:\n${raw}`);
  }
  seededPageUrl = resolved;
});

// ─── A. Golden path — completed run with findings ────────────────────────────
test('A: completed run with findings shows Showing: caption without console errors', async ({
  page,
}) => {
  const consoleErrors: string[] = [];
  const failedRequests: string[] = [];

  page.on('console', (msg) => {
    if (msg.type() === 'error' && !KNOWN_NOISE.some((n) => msg.text().includes(n))) {
      consoleErrors.push(msg.text());
    }
  });
  page.on('requestfailed', (req) => {
    const url = req.url();
    if (!KNOWN_NOISE.some((n) => url.includes(n))) {
      failedRequests.push(url);
    }
  });

  // Ensure summary has the `overall` block so MetricsOverview renders
  setAnalysisRunSummaryWithOverall(seedData.analysisRunId);
  setAnalysisRunCompleted(seedData.analysisRunId);

  try {
    await page.goto(`/sites/${sitePublicId}/analyze`, { waitUntil: 'networkidle' });

    // The "Showing:" window caption must appear when a completed run has dates + overall data
    await expect(page.getByText(/Showing:/)).toBeVisible({ timeout: 10_000 });

    // Header must identify the page (exact: true avoids matching the celebration h2)
    await expect(page.getByRole('heading', { name: 'Analysis', exact: true })).toBeVisible();

    // No error card should be showing
    await expect(page.getByText('Analysis failed', { exact: true })).not.toBeVisible();

    expect(consoleErrors).toHaveLength(0);
    expect(failedRequests).toHaveLength(0);
  } finally {
    clearAnalysisRunSummaryOverall(seedData.analysisRunId);
  }
});

// ─── B. Processing/pending state — skeleton placeholders ────────────────────
test('B: pending analysis run shows queued banner, not blank page', async ({ page }) => {
  const consoleErrors: string[] = [];
  const failedRequests: string[] = [];

  page.on('console', (msg) => {
    if (msg.type() === 'error' && !KNOWN_NOISE.some((n) => msg.text().includes(n))) {
      consoleErrors.push(msg.text());
    }
  });
  page.on('requestfailed', (req) => {
    const url = req.url();
    if (!KNOWN_NOISE.some((n) => url.includes(n))) {
      failedRequests.push(url);
    }
  });

  setAnalysisRunPending(seedData.analysisRunId);

  try {
    await page.goto(`/sites/${sitePublicId}/analyze`, { waitUntil: 'networkidle' });

    // AnalysisRunStatus renders "Analysis queued" for pending status
    await expect(page.getByText('Analysis queued')).toBeVisible({ timeout: 10_000 });

    // The sr-only aria-live announcer should reflect the running state
    const announcer = page.locator('#status-announcer');
    await expect(announcer).toHaveText('Analysis running…');

    // The "Showing:" window caption must NOT appear (run hasn't completed)
    await expect(page.getByText(/Showing:/)).not.toBeVisible();

    expect(consoleErrors).toHaveLength(0);
    expect(failedRequests).toHaveLength(0);
  } finally {
    setAnalysisRunCompleted(seedData.analysisRunId);
  }
});

// ─── C. Failed analysis — friendly error card + Retry button ────────────────
test('C: failed analysis shows friendly error card and Retry Analysis button', async ({
  page,
}) => {
  const consoleErrors: string[] = [];
  const failedRequests: string[] = [];

  page.on('console', (msg) => {
    if (msg.type() === 'error' && !KNOWN_NOISE.some((n) => msg.text().includes(n))) {
      consoleErrors.push(msg.text());
    }
  });
  page.on('requestfailed', (req) => {
    const url = req.url();
    if (!KNOWN_NOISE.some((n) => url.includes(n))) {
      failedRequests.push(url);
    }
  });

  setAnalysisRunFailed(seedData.analysisRunId, 'GSC API returned 403 Forbidden');

  try {
    await page.goto(`/sites/${sitePublicId}/analyze`, { waitUntil: 'networkidle' });

    // The error section header (uppercase, not the sr-only variant)
    await expect(page.getByText('Analysis failed', { exact: true })).toBeVisible({
      timeout: 10_000,
    });

    // errorMessages.ts maps 403/Forbidden → "GSC Connection Lost"
    await expect(page.getByText('GSC Connection Lost')).toBeVisible({ timeout: 5_000 });

    // User-friendly retry path
    await expect(page.getByRole('button', { name: /Retry Analysis/i })).toBeVisible();

    // Reconnect action (GSC 403 → reconnect link)
    await expect(page.getByRole('link', { name: /Reconnect/i })).toBeVisible();

    // No raw exception or stack trace
    await expect(page.getByText(/Illuminate\\/)).not.toBeVisible();
    await expect(page.getByText(/stack trace/i)).not.toBeVisible();

    expect(consoleErrors).toHaveLength(0);
    expect(failedRequests).toHaveLength(0);
  } finally {
    setAnalysisRunCompleted(seedData.analysisRunId);
  }
});

// ─── D. Low-data empty state — ran but below significance threshold ───────────
test('D: completed run with no findings shows below-threshold empty state, not blank', async ({
  page,
}) => {
  const consoleErrors: string[] = [];
  const failedRequests: string[] = [];

  page.on('console', (msg) => {
    if (msg.type() === 'error' && !KNOWN_NOISE.some((n) => msg.text().includes(n))) {
      consoleErrors.push(msg.text());
    }
  });
  page.on('requestfailed', (req) => {
    const url = req.url();
    if (!KNOWN_NOISE.some((n) => url.includes(n))) {
      failedRequests.push(url);
    }
  });

  // Need summary.overall so MetricsOverview renders, but zero findings
  setAnalysisRunSummaryWithOverall(seedData.analysisRunId);
  setAnalysisRunCompleted(seedData.analysisRunId);
  clearFindings(seedData.analysisRunId);

  try {
    await page.goto(`/sites/${sitePublicId}/analyze`, { waitUntil: 'networkidle' });

    // Low-data empty state title — honest message about significance threshold
    await expect(
      page.getByText(/below our significance threshold/i),
    ).toBeVisible({ timeout: 10_000 });

    // "View Recommendations" CTA must exist — leads user AWAY from this page
    await expect(page.getByRole('link', { name: /View Recommendations/i })).toBeVisible();

    // Threshold settings link
    await expect(page.getByRole('link', { name: /Adjust thresholds/i })).toBeVisible();

    // The false "found actionable opportunities" celebration must NOT appear
    await expect(page.getByText(/found actionable opportunities/i)).not.toBeVisible();

    expect(consoleErrors).toHaveLength(0);
    expect(failedRequests).toHaveLength(0);
  } finally {
    restoreFindings(seedData.analysisRunId, seededPageUrl);
    clearAnalysisRunSummaryOverall(seedData.analysisRunId);
  }
});

// ─── E. Permission denied — unauthenticated access ───────────────────────────
test('E: unauthenticated access to analyze page redirects to /login', async ({ browser }) => {
  const ctx = await browser.newContext({ storageState: undefined });
  const page = await ctx.newPage();

  const failedRequests: string[] = [];
  page.on('requestfailed', (req) => {
    const url = req.url();
    if (!KNOWN_NOISE.some((n) => url.includes(n))) {
      failedRequests.push(url);
    }
  });

  try {
    await page.goto(`/sites/${sitePublicId}/analyze`, { waitUntil: 'networkidle' });

    // Must land on the login page — no data leak
    await expect(page).toHaveURL(/\/login/, { timeout: 10_000 });
    await expect(page.getByLabel(/email/i)).toBeVisible();

    expect(failedRequests).toHaveLength(0);
  } finally {
    await ctx.close();
  }
});
