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

import { login, apiHeaders } from '../smoke/helpers';
import { seedFullyLoadedDeal } from '../fixtures/sales-journey';

/**
 * EOI generation parity — the two pathways agree on field content.
 *
 * The CRM fills an EOI two ways, both driven by the *same* `EoiContext`
 * (`src/lib/services/eoi-context.ts`):
 *
 *   1. In-app pathway  — `fillEoiFormFields()` (src/lib/pdf/fill-eoi-form.ts)
 *      fills `assets/eoi-template.pdf`'s AcroForm fields and flattens.
 *   2. Documenso pathway — `buildDocumensoPayload()`
 *      (src/lib/services/documenso-payload.ts) emits `formValues` keyed
 *      by the *same field names*.
 *
 * Both consume identical derivation rules, so for the same EoiContext
 * the rendered field content must match field-for-field:
 *
 *   Name         = client.fullName
 *   Email        = client.primaryEmail
 *   Address      = "street, city, REGION, postal, COUNTRY-ISO"
 *   Yacht Name   = yacht.name              (blank when no yacht)
 *   Length/Width/Draft = "<n> <unit>"      (blank when unset)
 *   Berth Number = eoiBerthRange || primary mooring
 *   Purchase     = true,  Lease_10 = false (constant)
 *
 * `EoiContext` is computed once, server-side; the `/eoi-context` read
 * API returns it verbatim. This spec asserts that the *single shared
 * source* carries exactly the values both pathways are contractually
 * required to render, which is the parity guarantee. (The per-pathway
 * derivation functions themselves are unit-tested in
 * `tests/unit/pdf/fill-eoi-form.test.ts` and
 * `tests/unit/services/documenso-payload.test.ts`; this spec validates
 * the live end-to-end context they both feed off is internally
 * consistent for a real seeded deal.)
 */

interface EoiContextResponse {
  data: {
    client: {
      fullName: string;
      primaryEmail: string | null;
      address: {
        street: string;
        city: string;
        subdivision: string;
        postalCode: string;
        countryIso: string;
      } | null;
    };
    yacht: { name: string } | null;
    berth: { mooringNumber: string } | null;
    eoiBerthRange: string;
  };
}

/** Re-derive the `Berth Number` field value exactly as BOTH pathways do:
 *  `context.eoiBerthRange || (context.berth?.mooringNumber ?? '')`. */
function deriveBerthNumberField(ctx: EoiContextResponse['data']): string {
  return ctx.eoiBerthRange || (ctx.berth?.mooringNumber ?? '');
}

/** Re-derive the `Address` field value exactly as both `formatAddress`
 *  implementations do (identical in fill-eoi-form.ts and documenso-payload.ts):
 *  street, city, subdivision-suffix, postal, country-ISO joined by ", ". */
function deriveAddressField(ctx: EoiContextResponse['data']): string {
  const a = ctx.client.address;
  if (!a) return '';
  return [a.street, a.city, a.subdivision, a.postalCode, a.countryIso].filter(Boolean).join(', ');
}

test.describe('exhaustive: EOI pathway parity', () => {
  test.beforeEach(async ({ page }) => {
    await login(page, 'super_admin');
  });

  test('shared EoiContext carries the canonical field set both pathways render', async ({
    page,
  }) => {
    const headers = await apiHeaders(page);
    const deal = await seedFullyLoadedDeal(page, 'Parity');

    const res = await page.request.get(`/api/v1/interests/${deal.interestId}/eoi-context`, {
      headers,
    });
    expect(res.ok(), `eoi-context: ${res.status()} ${await res.text()}`).toBe(true);
    const { data } = (await res.json()) as EoiContextResponse;

    // ─── Section 2 (required, EOI hard-gate fields) ───
    // Name + Email come straight from the client; both pathways set
    // these identically with no transformation.
    expect(data.client.fullName).toBeTruthy();
    expect(data.client.primaryEmail).toMatch(/@example\.test$/);

    // ─── Berth Number (the parity-sensitive field) ───
    // Both pathways compute this with the identical expression.
    const berthNumberField = deriveBerthNumberField(data);
    expect(berthNumberField).toBe(deal.mooringNumber);
    // Single-berth deal: eoiBerthRange === primary mooring (no range collapse).
    expect(data.eoiBerthRange).toBe(deal.mooringNumber);

    // ─── Address (identical formatAddress in both pathways) ───
    // Seeded client has no address, so both pathways render an empty
    // Address field — and crucially, render the *same* empty string.
    const addressField = deriveAddressField(data);
    expect(typeof addressField).toBe('string');

    // ─── Yacht Name ───
    // A yacht is linked at seed time, so both pathways render its name.
    expect(data.yacht?.name).toBeTruthy();
  });

  test('Berth Number field is identical for the multi-berth range case', async ({ page }) => {
    const headers = await apiHeaders(page);
    const stamp = Date.now();

    const clientRes = await page.request.post('/api/v1/clients', {
      headers,
      data: {
        fullName: `Parity Multi ${stamp}`,
        contacts: [{ channel: 'email', value: `parity-${stamp}@example.test`, isPrimary: true }],
      },
    });
    expect(clientRes.ok()).toBe(true);
    const clientId = ((await clientRes.json()) as { data: { id: string } }).data.id;

    const yachtRes = await page.request.post('/api/v1/yachts', {
      headers,
      data: { name: `Parity Yacht ${stamp}`, owner: { type: 'client', id: clientId } },
    });
    expect(yachtRes.ok()).toBe(true);
    const yachtId = ((await yachtRes.json()) as { data: { id: string } }).data.id;

    const base = (stamp % 9000) + 1000;
    const moorings = [`P${base}`, `P${base + 1}`];
    const berthIds: string[] = [];
    for (const mooringNumber of moorings) {
      const berthRes = await page.request.post('/api/v1/berths', {
        headers,
        data: {
          mooringNumber,
          area: 'Parity Area',
          price: 90000,
          priceCurrency: 'EUR',
          tenureType: 'permanent',
        },
      });
      expect(berthRes.ok(), `berth ${mooringNumber}: ${berthRes.status()}`).toBe(true);
      berthIds.push(((await berthRes.json()) as { data: { id: string } }).data.id);
    }

    const interestRes = await page.request.post('/api/v1/interests', {
      headers,
      data: { clientId, yachtId, berthId: berthIds[0], pipelineStage: 'enquiry' },
    });
    expect(interestRes.ok()).toBe(true);
    const interestId = ((await interestRes.json()) as { data: { id: string } }).data.id;

    const addRes = await page.request.post(`/api/v1/interests/${interestId}/berths`, {
      headers,
      data: { berthId: berthIds[1], isSpecificInterest: false },
    });
    expect(addRes.ok(), `add berth: ${addRes.status()} ${await addRes.text()}`).toBe(true);

    const res = await page.request.get(`/api/v1/interests/${interestId}/eoi-context`, {
      headers,
    });
    expect(res.ok()).toBe(true);
    const { data } = (await res.json()) as EoiContextResponse;

    // Both pathways render the collapsed range as the Berth Number field —
    // the range wins over the bare primary mooring when the bundle has >1
    // berth. The expression is identical on both sides.
    const berthNumberField = deriveBerthNumberField(data);
    expect(berthNumberField).toBe(`P${base}-P${base + 1}`);
    // And it is the eoiBerthRange (not the primary mooring) that drives it.
    expect(data.eoiBerthRange).toBe(berthNumberField);
    expect(berthNumberField).not.toBe(data.berth?.mooringNumber);
  });
});
