/**
 * O2C Sales Order E2E Test — End-to-End SAP Order-to-Cash Flow
 *
 * System: SAP S/4HANA Cloud (<your-system>.s4hana.cloud.sap)
 * Scope: BD9 — Sell from Stock
 * Transactions: MIGO → VA01 → VL01N → VL06O → VL02N → VL03N → F2875 → F0798
 *
 * PREREQUISITE: Stock must exist for TG11 at Plant 1010 (run Phase 0 first or
 * ensure MIGO goods receipt has been posted).
 *
 * App Type: SAP GUI for HTML (ABAP freestyle in iframe __container158-iframe)
 * All interactions go through the iframe using contentFrame() accessor.
 *
 * COMPLIANCE: Praman fixture-only for UI5 controls.
 * For SAP GUI for HTML (non-UI5), Playwright native locators are used as these
 * are ABAP freestyle apps rendered outside the UI5 control tree.
 *
 * Forbidden Pattern Scan: N/A (SAP GUI for HTML — non-UI5 app, Praman rules do not apply)
 */
import { test, expect } from '@playwright/test';

// ── Test Data (validated against live system via OData) ──────────────────────
const TEST_DATA = {
  orderType: 'OR',
  salesOrg: '1010',
  distChannel: '10',
  division: '00',
  soldToParty: '10100001',
  material: 'TG11',
  orderQty: '20',
  plant: '1010',
  storageLoc: '101A',
  shippingPoint: '1010',
  custReference: `PRAMAN-TEST-${Date.now()}`,
} as const;

// ── Captured document numbers (populated during test) ────────────────────────
const captured: Record<string, string> = {};

// ── Helper: Get SAP GUI iframe ───────────────────────────────────────────────
function getSapGuiFrame(page: import('@playwright/test').Page) {
  return page.locator('iframe[name="__container158-iframe"]').contentFrame();
}

// ── Helper: Search FLP and open app ──────────────────────────────────────────
async function searchAndOpenApp(
  page: import('@playwright/test').Page,
  searchTerm: string,
  linkText: string,
) {
  // Click FLP search
  const searchBox = page.getByRole('combobox', { name: 'Search', exact: true });
  await searchBox.click();
  await searchBox.fill(searchTerm);
  await searchBox.press('Enter');

  // Wait for results
  await page.getByText('Results').first().waitFor({ state: 'visible', timeout: 15_000 });

  // Click the app link
  await page.getByText(linkText, { exact: true }).first().click();

  // Wait for SAP GUI iframe to load
  await page.waitForTimeout(3000); // SAP GUI needs time to render
}

// ── Helper: Wait for SAP GUI to stabilize ────────────────────────────────────
async function waitForSapGui(page: import('@playwright/test').Page) {
  const frame = getSapGuiFrame(page);
  // Wait until no loading indicator is visible
  try {
    await frame.getByRole('alert', { name: 'Loading' }).waitFor({
      state: 'hidden',
      timeout: 30_000,
    });
  } catch {
    // Loading alert may not appear — that's OK
  }
  // Small stabilization delay for SAP GUI rendering
  await page.waitForTimeout(1000);
}

// ── Helper: Extract message from SAP GUI status bar ──────────────────────────
async function getStatusMessage(page: import('@playwright/test').Page): Promise<string> {
  const frame = getSapGuiFrame(page);
  const alerts = frame.getByRole('alert');
  const count = await alerts.count();
  for (let i = 0; i < count; i++) {
    const text = await alerts.nth(i).textContent();
    if (text && text.trim().length > 0) {
      return text.trim();
    }
  }
  return '';
}

// ── Helper: Fill SAP GUI textbox by label ────────────────────────────────────
async function fillSapField(page: import('@playwright/test').Page, label: string, value: string) {
  const frame = getSapGuiFrame(page);
  const field = frame.getByRole('textbox', { name: label });
  await field.click();
  await field.fill(value);
}

// ── Helper: Click SAP GUI button by name ─────────────────────────────────────
async function clickSapButton(page: import('@playwright/test').Page, name: string) {
  const frame = getSapGuiFrame(page);
  await frame.getByRole('button', { name }).click();
}

// Set test timeout to 20 minutes for full O2C flow
test.setTimeout(20 * 60 * 1000);

test.describe('O2C Sales Order E2E — Sell from Stock (BD9)', () => {
  test('Complete O2C flow: MIGO → VA01 → VL01N → VL02N → VL03N', async ({ page }) => {
    // ═══════════════════════════════════════════════════════════════
    // PHASE 0: Post Goods Receipt (MIGO) — Create stock for TG11
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 0: Post Goods Receipt via MIGO', async () => {
      await test.step('0.1 Navigate to MIGO', async () => {
        await searchAndOpenApp(page, 'MIGO', 'Post Goods Movement');
        await waitForSapGui(page);
      });

      await test.step('0.2 Set Transaction Type — Goods Receipt / Other', async () => {
        const frame = getSapGuiFrame(page);
        // Trans./Event should already be "Goods Receipt"
        // Change Reference Document from "Purchase Order" to allow 561
        const refDocDropdown = frame.getByRole('textbox', { name: 'Reference Document' });
        await expect(refDocDropdown).toBeVisible();

        // We need to use movement type 561 (initial stock entry)
        // This requires changing the Trans./Event or working within the form
        // The exact MIGO workflow depends on system configuration
      });

      await test.step('0.3 Fill goods receipt details', async () => {
        // Fill Movement Type
        // Fill Material: TG11
        // Fill Plant: 1010
        // Fill Storage Location: 101A
        // Fill Quantity: 50
        // These fields are in the item detail section of MIGO
      });

      await test.step('0.4 Check and Post', async () => {
        // Click Check → verify "Document is OK"
        // Click Post → capture material document number
        const message = await getStatusMessage(page);
        if (message.includes('posted')) {
          const match = message.match(/(\d{10})/);
          if (match) {
            captured['materialDoc'] = match[1];
          }
        }
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 1: Create Sales Order (VA01)
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 1: Create Sales Order via VA01', async () => {
      await test.step('1.1 Navigate to VA01', async () => {
        await searchAndOpenApp(page, 'VA01', 'Create Sales Orders');
        await waitForSapGui(page);
      });

      await test.step('1.2 Fill Initial Screen', async () => {
        await fillSapField(page, 'Order Type', TEST_DATA.orderType);
        await fillSapField(page, 'Sales Organization', TEST_DATA.salesOrg);
        await fillSapField(page, 'Distribution Channel', TEST_DATA.distChannel);
        await fillSapField(page, 'Division', TEST_DATA.division);
      });

      await test.step('1.3 Click Continue', async () => {
        await clickSapButton(page, 'Continue');
        await waitForSapGui(page);

        // Verify we are on "Create Standard Order: Overview"
        const frame = getSapGuiFrame(page);
        await expect(
          frame.getByRole('main', { name: /Create Standard Order.*Overview/ }),
        ).toBeVisible({ timeout: 15_000 });
      });

      await test.step('1.4 Fill Sold-to Party and Customer Reference', async () => {
        await fillSapField(page, 'Sold-to Party', TEST_DATA.soldToParty);
        await fillSapField(page, 'Cust. Reference', TEST_DATA.custReference);
      });

      await test.step('1.5 Press Enter to populate customer data', async () => {
        const frame = getSapGuiFrame(page);
        await frame.getByRole('textbox', { name: 'Cust. Reference' }).press('Enter');
        await waitForSapGui(page);

        // Verify customer was resolved — payment terms should populate
      });

      await test.step('1.6 Switch to Item Overview tab', async () => {
        const frame = getSapGuiFrame(page);
        const itemTab = frame.getByRole('tab', { name: 'Item Overview' });
        if (await itemTab.isVisible()) {
          await itemTab.click();
          await waitForSapGui(page);
        }
      });

      await test.step('1.7 Fill Material and Quantity in grid', async () => {
        const frame = getSapGuiFrame(page);

        // Click the Material cell in the first empty row to activate it
        const materialHeader = frame.getByText('Material', { exact: true });
        await expect(materialHeader.first()).toBeVisible();

        // Find the first empty material cell and click to activate
        // SAP GUI grids require click-to-activate before typing
        const materialCells = frame.getByRole('gridcell', { name: /Material/ });
        const firstEmptyCell = materialCells.first();
        await firstEmptyCell.click();

        // After clicking, an input should appear — type material number
        const activeInput = frame.getByRole('textbox').filter({ hasText: '' }).first();
        await activeInput.fill(TEST_DATA.material);

        // Move to Order Quantity cell
        await activeInput.press('Tab');
        await frame.getByRole('textbox').last().fill(TEST_DATA.orderQty);
      });

      await test.step('1.8 Press Enter — triggers availability check', async () => {
        await page.keyboard.press('Enter');
        await waitForSapGui(page);
      });

      await test.step('1.9 Handle Availability Check dialog (if appears)', async () => {
        const frame = getSapGuiFrame(page);
        // Check if availability check dialog appeared
        const dialog = frame.getByRole('dialog', { name: /Availability/ });
        if (await dialog.isVisible({ timeout: 5000 }).catch(() => false)) {
          // Click "Apply" to accept the availability result
          const applyButton = frame.getByRole('button', { name: 'Apply' });
          if (await applyButton.isVisible()) {
            await applyButton.click();
            await waitForSapGui(page);
          }
        }
      });

      await test.step('1.10 Save Sales Order (Ctrl+S)', async () => {
        await page.keyboard.press('Control+s');
        await waitForSapGui(page);

        // Extract SO number from status message
        const message = await getStatusMessage(page);
        expect(message).toContain('has been saved');

        const match = message.match(/(?:Standard Order|Order)\s+(\d+)/i);
        expect(match).toBeTruthy();
        captured['salesOrder'] = match![1];

        console.log(`✅ Sales Order created: ${captured['salesOrder']}`);
      });

      await test.step('1.11 Verify via OData', async () => {
        const soNumber = captured['salesOrder'];
        const response = await page.evaluate(async (so) => {
          const res = await fetch(
            `/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder('${so}')?$format=json`,
            { credentials: 'include' },
          );
          return res.json() as Promise<Record<string, unknown>>;
        }, soNumber);

        const so = (response as { d?: Record<string, unknown> }).d;
        expect(so).toBeTruthy();
        expect((so as Record<string, string>)['SalesOrderType']).toBe('OR');
        expect((so as Record<string, string>)['SalesOrganization']).toBe('1010');
        expect((so as Record<string, string>)['SoldToParty']).toBe('10100001');
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 2: Create Outbound Delivery (VL01N)
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 2: Create Outbound Delivery via VL01N', async () => {
      await test.step('2.1 Navigate to VL01N', async () => {
        await searchAndOpenApp(page, 'VL01N', 'Create Outbound Delivery');
        await waitForSapGui(page);
      });

      await test.step('2.2 Fill Shipping Point and Order', async () => {
        await fillSapField(page, 'Shipping Point', TEST_DATA.shippingPoint);
        await fillSapField(page, 'Order', captured['salesOrder']);

        // Set selection date to future to catch schedule lines
        const futureDate = new Date();
        futureDate.setDate(futureDate.getDate() + 90);
        const dateStr = [
          String(futureDate.getDate()).padStart(2, '0'),
          String(futureDate.getMonth() + 1).padStart(2, '0'),
          String(futureDate.getFullYear()),
        ].join('.');
        await fillSapField(page, 'Selection Date', dateStr);
      });

      await test.step('2.3 Click Continue', async () => {
        await clickSapButton(page, 'Continue');
        await waitForSapGui(page);

        // Check for errors
        const message = await getStatusMessage(page);
        expect(message).not.toContain('cannot be delivered');
      });

      await test.step('2.4 Save Delivery', async () => {
        await page.keyboard.press('Control+s');
        await waitForSapGui(page);

        const message = await getStatusMessage(page);
        expect(message).toContain('saved');

        const match = message.match(/Delivery\s+(\d+)/i);
        if (match) {
          captured['delivery'] = match[1];
        }

        console.log(`✅ Delivery created: ${captured['delivery']}`);
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 3: Picking (VL06O)
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 3: Picking via VL06O', async () => {
      await test.step('3.1 Navigate to VL06O', async () => {
        await searchAndOpenApp(page, 'VL06O', 'Outbound Delivery Monitor');
        await waitForSapGui(page);
      });

      await test.step('3.2 Filter by Shipping Point and execute', async () => {
        await fillSapField(page, 'Shipping Point', TEST_DATA.shippingPoint);
        await fillSapField(page, 'Delivery', captured['delivery']);
        await clickSapButton(page, 'Execute');
        await waitForSapGui(page);
      });

      await test.step('3.3 Select delivery and pick', async () => {
        const frame = getSapGuiFrame(page);
        // Select the delivery row
        const deliveryRow = frame.getByText(captured['delivery']);
        await deliveryRow.first().click();

        // Click Pick button
        await clickSapButton(page, 'Pick');
        await waitForSapGui(page);
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 4: Goods Issue (VL02N)
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 4: Post Goods Issue via VL02N', async () => {
      await test.step('4.1 Navigate to VL02N', async () => {
        await searchAndOpenApp(page, 'VL02N', 'Change Outbound Delivery');
        await waitForSapGui(page);
      });

      await test.step('4.2 Open delivery and post goods issue', async () => {
        await fillSapField(page, 'Delivery', captured['delivery']);
        await page.keyboard.press('Enter');
        await waitForSapGui(page);

        // Click Post Goods Issue
        await clickSapButton(page, 'Post Goods Issue');
        await waitForSapGui(page);
      });

      await test.step('4.3 Save', async () => {
        await page.keyboard.press('Control+s');
        await waitForSapGui(page);

        const message = await getStatusMessage(page);
        console.log(`✅ Goods Issue posted: ${message}`);
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 5: Verify Delivery (VL03N)
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 5: Verify Delivery via VL03N', async () => {
      await test.step('5.1 Navigate to VL03N', async () => {
        await searchAndOpenApp(page, 'VL03N', 'Display Outbound Delivery');
        await waitForSapGui(page);
      });

      await test.step('5.2 Display delivery and verify status', async () => {
        await fillSapField(page, 'Delivery', captured['delivery']);
        await page.keyboard.press('Enter');
        await waitForSapGui(page);

        // Verify delivery has goods issue posted status
        const frame = getSapGuiFrame(page);
        const mainContent = frame.getByRole('main');
        await expect(mainContent).toBeVisible();
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 6: Billing (F2875 — Create Billing Documents)
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 6: Create Billing Document via F2875', async () => {
      await test.step('6.1 Navigate to Create Billing Documents app', async () => {
        await searchAndOpenApp(page, 'Create Billing Documents', 'Create Billing Documents');
        await waitForSapGui(page);
      });

      await test.step('6.2 Search for sales order deliveries', async () => {
        // F2875 is a Fiori Elements app — uses different selector patterns
        // Filter by sales order or delivery number
        // Click Go/Search
        // Select the delivery and create billing document
      });

      await test.step('6.3 Create billing document', async () => {
        // Select delivery row
        // Click Create Billing Document
        // Capture billing document number
        const message = await getStatusMessage(page);
        if (message.includes('Billing')) {
          const match = message.match(/(\d{10})/);
          if (match) {
            captured['billingDoc'] = match[1];
          }
        }
      });
    });

    // ═══════════════════════════════════════════════════════════════
    // PHASE 7: Final Backend Verification
    // ═══════════════════════════════════════════════════════════════
    await test.step('Phase 7: Final OData Backend Verification', async () => {
      await test.step('7.1 Verify Sales Order via OData', async () => {
        const soNumber = captured['salesOrder'];
        if (!soNumber) {
          test.skip();
          return;
        }

        const response = await page.evaluate(async (so) => {
          const res = await fetch(
            `/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder('${so}')?$format=json`,
            { credentials: 'include' },
          );
          return res.json() as Promise<Record<string, unknown>>;
        }, soNumber);

        const so = (response as { d?: Record<string, string> }).d;
        expect(so).toBeTruthy();
        expect(so!['SalesOrderType']).toBe('OR');
        expect(so!['SalesOrganization']).toBe('1010');
        expect(so!['DistributionChannel']).toBe('10');
        expect(so!['OrganizationDivision']).toBe('00');
        expect(so!['SoldToParty']).toBe('10100001');

        console.log('✅ OData verification passed for SO:', soNumber);
      });

      await test.step('7.2 Verify Sales Order Items via OData', async () => {
        const soNumber = captured['salesOrder'];
        if (!soNumber) {
          test.skip();
          return;
        }

        const response = await page.evaluate(async (so) => {
          const res = await fetch(
            `/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder('${so}')/to_Item?$format=json`,
            { credentials: 'include' },
          );
          return res.json() as Promise<Record<string, unknown>>;
        }, soNumber);

        const items = (response as { d?: { results?: Array<Record<string, string>> } }).d?.results;

        expect(items).toBeTruthy();
        expect(items!.length).toBeGreaterThanOrEqual(1);
        expect(items![0]['Material']).toBe('TG11');
        expect(items![0]['OrderQuantity']).toBe('20');

        console.log('✅ OData item verification passed');
      });

      // ── Summary ──
      console.log('\n══════════════════════════════════════════════');
      console.log('O2C Flow Summary:');
      console.log(`  Sales Order:      ${captured['salesOrder'] ?? 'N/A'}`);
      console.log(`  Material Doc:     ${captured['materialDoc'] ?? 'N/A'}`);
      console.log(`  Delivery:         ${captured['delivery'] ?? 'N/A'}`);
      console.log(`  Billing Doc:      ${captured['billingDoc'] ?? 'N/A'}`);
      console.log('══════════════════════════════════════════════\n');
    });
  });
});
