import { test, expect } from '@playwright/test';
import path from 'node:path';

/**
 * T-E2E-001 — the two-step Clockify import happy path (imports.md §H/§I):
 * upload a synthetic CSV on /import, resolve unknown entities on
 * /import/resolve with create-by-default decisions, commit, then assert
 * BEHAVIOR:
 *   - the post-import summary card quotes matched / unmatched counts;
 *   - the row whose Email is unknown lands Blocked on /entries.
 *
 * The fixture (e2e/fixtures/clockify-sample.csv) has 3 rows for a
 * plausibly-known worker (info@adipramono.com → Adi) and 1 row for an
 * unknown email (casey.newcomer@unknown-worker.example). The unknown-email
 * row imports with worker_party_id = NULL, which the eligibility predicate
 * (services/reporting/filters.py `_broken()`) classifies as Blocked.
 */

// __dirname is available: the e2e specs compile as CJS (no package.json at
// the repo root declaring `type: module`).
const CSV_FIXTURE = path.join(__dirname, 'fixtures', 'clockify-sample.csv');
const BATCH_NAME = 'E2E Smoke Import';

test.describe('T-E2E-001 import → resolve → commit', () => {
  test('imports a Clockify CSV and blocks the unknown-email row', async ({ page }) => {
    // --- /import : upload -------------------------------------------------
    await page.goto('/import');
    await expect(page.getByRole('heading', { level: 1, name: 'Import' })).toBeVisible();
    await expect(page.getByRole('heading', { name: 'Upload a Clockify CSV' })).toBeVisible();

    // SELECTOR-REVIEW: there is exactly one <input type=file> on /import
    // (ImportPage.tsx UploadPanel). getByLabel('CSV file') also works via
    // the wrapping <label><span>CSV file</span>…</label>.
    await page.locator('input[type="file"]').setInputFiles(CSV_FIXTURE);

    // A batch name makes the /import/resolve <h1> deterministic.
    await page.getByPlaceholder('auto-named from the CSV date range').fill(BATCH_NAME);
    await page.getByRole('button', { name: 'Upload and import' }).click();

    // --- /import/resolve : decide (create-by-default) + commit ------------
    await page.waitForURL(/\/import\/resolve\?id=/);
    await expect(page.getByRole('heading', { level: 1, name: BATCH_NAME })).toBeVisible();

    // Our fixture rows all carry a full operator/client/project triple, so
    // there are no "missing fields" buckets — only new-entity decisions,
    // which default to Create. If Brentwood/PlusROI already exist in the
    // seed graph the project names are still new, so a "New projects"
    // section is expected; assert its first row defaults to Create.
    // (Guarded: if EVERYTHING is already known there are no sections and the
    // button reads plain "Import".)
    const newProjects = page.getByRole('group', { name: 'New projects' });
    if ((await newProjects.count()) > 0) {
      // SELECTOR-REVIEW: MapCreateRow radios are labelled by their adjacent
      // <span> — the create radio's accessible name is `Create new "<name>"`.
      const createRadio = newProjects.getByRole('radio', { name: /^Create new/ }).first();
      await expect(createRadio).toBeChecked();
    }
    // SELECTOR-REVIEW: to also exercise a Map decision, a human can select a
    // "Map to existing:" radio + dropdown here — that needs a known
    // candidate at the right scope, which depends on the seeded catalog.

    // The commit button reads "Import" (all known) or "Apply decisions and
    // import" (has decisions); while committing it reads "Importing…".
    await page
      .getByRole('button', { name: /^(import|apply decisions and import)$/i })
      .click();

    // --- /import?import_id= : the verified summary card -------------------
    await page.waitForURL(/\/import\?import_id=/);
    const batchId = new URL(page.url()).searchParams.get('import_id');
    expect(batchId).toBeTruthy();

    const summary = page.locator('.import-summary'); // role="status"
    await expect(summary).toContainText('Import complete.');
    await expect(summary).toContainText(/matched/); // "<n> matched"
    // At least the unknown-email row is unmatched, so the "· <n> unmatched"
    // clause always renders.
    await expect(summary).toContainText(/unmatched/);

    // --- /entries : the unknown-email row is Blocked ----------------------
    await page.goto(`/entries?batch=${batchId}&status=blocked&date=all`);
    await expect(page.getByRole('heading', { level: 1, name: 'Entries' })).toBeVisible();

    // StatusIcon for a blocked row is a <span class="status-blocked"
    // title="Blocked: …">⚠</span> (EntriesPage.tsx).
    // SELECTOR-REVIEW: asserts on the title prefix, not the ⚠ glyph.
    await expect(page.getByTitle(/^Blocked:/).first()).toBeVisible();
    // And the filter is genuinely non-empty (no "empty" placeholder row).
    await expect(page.getByText('No entries match this filter.')).toHaveCount(0);
  });
});
