/**
 * Journey: Cannibalization Show — R6PROD-006 consolidation workflow regression spec
 *
 * Covers changed files:
 *  - resources/js/Pages/Cannibalization/Show.tsx  (R6PROD-006: Consolidate CTA + dialog)
 *
 * == States covered ==
 *  A. Golden path  — show page loads, heading visible, competing pages table renders,
 *                    Consolidate CTA visible, no JS errors, no failed XHR
 *  B. Consolidate dialog — clicking Consolidate opens preview dialog with winner/losers
 *  C. Consolidated state — when consolidation_status is 'draft_generated', CTA is replaced
 *                          by a "Draft generated" badge
 *  D. Permission denied — unauthenticated access redirects to /login
 *
 * == public_id routing ==
 * HasPublicId makes site and cannibalizationCase route params ULIDs.
 * seed-data.json carries the integer siteId; we resolve the ULID via artisan tinker
 * in beforeAll, then create cannibalization test fixtures tied to the seeded site.
 *
 * == serial mode ==
 * test.describe.configure({ mode: 'serial' }) prevents worker restarts between
 * nested describe blocks (which would re-fire beforeAll with a stale snapshot).
 */

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

// Serialize all tests in this file — prevents worker restarts that cause
// beforeAll to fire multiple times with a stale seed snapshot.
test.describe.configure({ mode: 'serial' });

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Run a PHP snippet via artisan tinker using a temp file to avoid quoting issues. */
function runPhp(phpCode: string): string {
  const tmpFile = path.join('/tmp', `e2e_cannibalization_${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 {
    fs.unlinkSync(tmpFile);
  }
}

/** Resolves the ULID public_id for a site given its integer PK. */
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;
}

/**
 * Creates a CannibalizationRun + CannibalizationCase (with primary_page_url)
 * + 2 competing pages tied to the E2E seed site.
 * Returns { casePublicId }.
 */
function createCannibalizationFixtures(siteId: number): { casePublicId: string } {
  const raw = runPhp(`
$site = \\App\\Models\\Site::find(${siteId});
$run = \\App\\Models\\CannibalizationRun::factory()->create([
    'site_id' => $site->id,
    'status' => \\App\\Enums\\JobStatus::Completed,
    'completed_at' => now(),
]);
$case = \\App\\Models\\CannibalizationCase::factory()
    ->withPrimaryPage('https://e2e-test.example.com/blog/seo-tips')
    ->create([
        'cannibalization_run_id' => $run->id,
        'query_cluster' => 'seo optimization tips e2e',
        'competing_pages_count' => 2,
        'impact_score' => 75.5,
        'confidence_score' => 80.0,
        'lead_volatility_score' => 0.6,
        'status' => 'open',
        'lifecycle_status' => 'active',
    ]);
\\App\\Models\\CannibalizationCasePage::factory()->create([
    'case_id' => $case->id,
    'page_url' => 'https://e2e-test.example.com/blog/seo-tips',
    'page_url_hash' => hash('sha256', 'https://e2e-test.example.com/blog/seo-tips'),
    'is_primary' => true,
    'impressions_share' => 0.65,
    'clicks_share' => 0.70,
    'avg_position' => 4.5,
]);
\\App\\Models\\CannibalizationCasePage::factory()->create([
    'case_id' => $case->id,
    'page_url' => 'https://e2e-test.example.com/blog/seo-guide',
    'page_url_hash' => hash('sha256', 'https://e2e-test.example.com/blog/seo-guide'),
    'is_primary' => false,
    'impressions_share' => 0.35,
    'clicks_share' => 0.30,
    'avg_position' => 8.2,
]);
echo 'CASE_PUBLIC_ID=' . $case->public_id;
`);
  const match = raw.trim().match(/CASE_PUBLIC_ID=([A-Z0-9]{26})/);
  if (!match) {
    throw new Error(`createCannibalizationFixtures failed. Raw:\n${raw}`);
  }
  return { casePublicId: match[1] };
}

/**
 * Creates a CannibalizationCase with consolidation_status = 'draft_generated'
 * to test the consolidated state CTA replacement.
 */
function createConsolidatedCaseFixture(siteId: number, runId: string): { casePublicId: string } {
  const raw = runPhp(`
$run = \\App\\Models\\CannibalizationRun::where('id', ${runId})->first();
if (!$run) {
    // fallback: use any run for this site
    $site = \\App\\Models\\Site::find(${siteId});
    $run = \\App\\Models\\CannibalizationRun::factory()->create([
        'site_id' => $site->id,
        'status' => \\App\\Enums\\JobStatus::Completed,
        'completed_at' => now(),
    ]);
}
$case = \\App\\Models\\CannibalizationCase::factory()
    ->withPrimaryPage('https://e2e-test.example.com/blog/already-consolidated')
    ->create([
        'cannibalization_run_id' => $run->id,
        'query_cluster' => 'already consolidated query e2e',
        'consolidation_status' => 'draft_generated',
        'status' => 'open',
        'lifecycle_status' => 'active',
    ]);
echo 'CONSOLIDATED_CASE_ID=' . $case->public_id;
`);
  const match = raw.trim().match(/CONSOLIDATED_CASE_ID=([A-Z0-9]{26})/);
  if (!match) {
    throw new Error(`createConsolidatedCaseFixture failed. Raw:\n${raw}`);
  }
  return { casePublicId: match[1] };
}

// Known background noise (same pattern as other journey specs).
// - /api/lead-score/pql-signal: 419 CSRF when APP_URL differs from test base URL.
// - /profile: background XHR that may 4xx during seeding.
// - ERR_TOO_MANY_REDIRECTS: redirect-loop on background polling when APP_URL is HTTPS.
const KNOWN_NOISE = ['/api/lead-score/pql-signal', '/profile', 'ERR_TOO_MANY_REDIRECTS'];

function isNoise(text: string): boolean {
  return (
    text.includes('extension') ||
    text.includes('chrome-extension') ||
    KNOWN_NOISE.some((n) => text.includes(n))
  );
}

// Shared across describe blocks (resolved in beforeAll)
let sitePublicId = '';
let casePublicId = '';
let consolidatedCasePublicId = '';
let runId = '';

// ---------------------------------------------------------------------------
// Suite A: Golden path — page load, heading, competing pages table
// ---------------------------------------------------------------------------

test.describe('Journey: Cannibalization Show — A: golden path', () => {
  test.beforeAll(async ({ seedData }: { seedData: E2ESeedData }) => {
    sitePublicId = resolveSitePublicId(seedData.siteId);
    const fixtures = createCannibalizationFixtures(seedData.siteId);
    casePublicId = fixtures.casePublicId;
  });

  test('show page loads without uncaught JS errors or console.error', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    const errors: string[] = [];
    page.on('pageerror', (err) => errors.push(`pageerror: ${err.message}`));
    page.on('console', (msg) => {
      if (msg.type() === 'error') errors.push(`console.error: ${msg.text()}`);
    });

    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    await expect(page).toHaveURL(
      new RegExp(`/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`),
    );

    const appErrors = errors.filter((e) => !isNoise(e));
    expect(appErrors, `Uncaught errors:\n${appErrors.join('\n')}`).toHaveLength(0);
  });

  test('no failed XHR requests (4xx/5xx) during page load', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    const failedRequests: string[] = [];
    page.on('response', (r) => {
      if (r.status() >= 400 && !isNoise(r.url())) {
        failedRequests.push(`${r.status()} ${r.url()}`);
      }
    });

    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    expect(
      failedRequests,
      `Failed XHR requests:\n${failedRequests.join('\n')}`,
    ).toHaveLength(0);
  });

  test('renders the query cluster as the page heading', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    // The query_cluster is rendered as the DetailPageHeader title
    await expect(
      page.getByRole('heading', { name: /seo optimization tips e2e/i }),
    ).toBeVisible();
  });

  test('renders the competing pages table with page rows', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    // Table heading
    await expect(page.getByRole('heading', { name: /competing pages/i })).toBeVisible();

    // Both seeded page URLs appear in the table
    await expect(page.getByText(/seo-tips/)).toBeVisible();
    await expect(page.getByText(/seo-guide/)).toBeVisible();
  });

  test('renders Case Overview with impact and confidence scores', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    await expect(page.getByRole('heading', { name: /case overview/i })).toBeVisible();
    // Impact score rendered as "75.5/100" or similar
    await expect(page.getByText(/\/100/)).toBeVisible();
  });

  test('renders status action buttons for an open case', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    // Open case shows "Mark resolved" and "Dismiss"
    await expect(page.getByRole('button', { name: /mark resolved/i })).toBeVisible();
    await expect(page.getByRole('button', { name: /dismiss/i })).toBeVisible();
  });
});

// ---------------------------------------------------------------------------
// Suite B: R6PROD-006 — Consolidate CTA and preview dialog
// ---------------------------------------------------------------------------

test.describe('Journey: Cannibalization Show — B: Consolidate CTA (R6PROD-006)', () => {
  test.beforeAll(async ({ seedData }: { seedData: E2ESeedData }) => {
    // casePublicId is set from Suite A's beforeAll (serial mode, shared module scope)
    // If somehow not set (first run in isolation), create fixtures again.
    if (!sitePublicId) {
      sitePublicId = resolveSitePublicId(seedData.siteId);
    }
    if (!casePublicId) {
      const fixtures = createCannibalizationFixtures(seedData.siteId);
      casePublicId = fixtures.casePublicId;
    }
  });

  test('renders the Consolidate CTA button when primary_page_url is set and not yet consolidated', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
      { waitUntil: 'networkidle' },
    );

    // The case has primary_page_url set and consolidation_status is null → CTA visible
    await expect(page.getByRole('button', { name: /consolidate/i })).toBeVisible();
  });
});

// ---------------------------------------------------------------------------
// Suite C: Consolidated state — CTA replaced by status badge
// ---------------------------------------------------------------------------

test.describe('Journey: Cannibalization Show — C: consolidated state badge', () => {
  test.beforeAll(async ({ seedData }: { seedData: E2ESeedData }) => {
    if (!sitePublicId) {
      sitePublicId = resolveSitePublicId(seedData.siteId);
    }
    // Reuse the run from the existing case fixture (resolve via latest run for site)
    const rawRunId = runPhp(
      `$r = \\App\\Models\\CannibalizationRun::where('site_id', ${seedData.siteId})->first(); echo $r ? $r->id : '1';`,
    );
    runId = rawRunId.trim().split('\n').pop()?.trim() ?? '1';
    const fixtures = createConsolidatedCaseFixture(seedData.siteId, runId);
    consolidatedCasePublicId = fixtures.casePublicId;
  });

  test('shows "Draft generated" badge instead of Consolidate CTA when already consolidated', async ({
    page,
  }: {
    page: import('@playwright/test').Page;
  }) => {
    await page.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${consolidatedCasePublicId}`,
      { waitUntil: 'networkidle' },
    );

    // Consolidate CTA should NOT appear — case is already in draft_generated state
    await expect(page.getByRole('button', { name: /consolidate/i })).not.toBeVisible();

    // Status badge "Draft generated" should appear
    await expect(page.getByText(/draft generated/i).first()).toBeVisible();
  });
});

// ---------------------------------------------------------------------------
// Suite D: Permission denied — unauthenticated access
// ---------------------------------------------------------------------------

test.describe('Journey: Cannibalization Show — D: permission denied (unauthenticated)', () => {
  test.beforeAll(async ({ seedData }: { seedData: E2ESeedData }) => {
    if (!sitePublicId) {
      sitePublicId = resolveSitePublicId(seedData.siteId);
    }
    if (!casePublicId) {
      const fixtures = createCannibalizationFixtures(seedData.siteId);
      casePublicId = fixtures.casePublicId;
    }
  });

  test('unauthenticated access to cannibalization show page redirects to /login', async ({
    browser,
  }: {
    browser: import('@playwright/test').Browser;
  }) => {
    const ctx = await browser.newContext({ storageState: { cookies: [], origins: [] } });
    const p = await ctx.newPage();

    await p.goto(
      `/sites/${sitePublicId}/content-intelligence/cannibalization/${casePublicId}`,
    );

    await expect(p).toHaveURL(/\/login/);
    await ctx.close();
  });
});
