import type { Browser, BrowserContext, Page } from 'playwright';

const PUBLIC_URL = 'https://www.sat.gob.mx/portal/public/tramites/constancia-de-situacion-fiscal';

export interface CsfLoginSession {
  context: BrowserContext;
  appPage: Page;
}

/**
 * Navigates from the public CSF page → "SERVICIO" popup → FIEL login →
 * returns the post-login app page (popup that became the SPA).
 * Ver referencia_sat_portal_csf: el botón "Generar" vive en un iframe JSF
 * dentro de esta appPage, por eso la retornamos tal cual.
 */
export async function loginSatCsf(
  browser: Browser,
  cerPath: string,
  keyPath: string,
  password: string,
  knownRfc?: string,
): Promise<CsfLoginSession> {
  const context = await browser.newContext({
    acceptDownloads: true,
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
  });
  await context.addInitScript(() => {
    Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
  });
  const publicPage = await context.newPage();
  publicPage.setDefaultTimeout(60_000);

  await publicPage.goto(PUBLIC_URL, { waitUntil: 'networkidle', timeout: 120_000 });
  await publicPage.waitForTimeout(3000);

  // Click acordeón "Obtén tu constancia" / "Obtener constancia"
  const obtenerLocator = publicPage.locator(
    'text=/Obt[eé]n\\s+la\\s+constancia|Obt[eé]n\\s+tu\\s+constancia|Obtener\\s+constancia|Obtener\\s+la\\s+constancia/i',
  ).first();
  await obtenerLocator.waitFor({ state: 'visible', timeout: 120_000 });
  await obtenerLocator.scrollIntoViewIfNeeded();
  await obtenerLocator.click();
  await publicPage.waitForTimeout(1500);

  // Click "SERVICIO" → popup
  const popupPromise = context.waitForEvent('page', { timeout: 120_000 });
  await publicPage.locator('text=/^\\s*SERVICIO\\s*$/i').first().click();
  const loginPage = await popupPromise;
  await loginPage.waitForLoadState('domcontentloaded');
  loginPage.setDefaultTimeout(60_000);

  // Click "e.firma" (NO "e.firma portable"). El SAT a veces aterriza en la
  // pestaña de contraseña: el botón cambia a la vista FIEL. El click sintético
  // de Playwright a veces no dispara el handler — afirmamos el efecto (aparece
  // el file input) y reintentamos con dispatchEvent si hace falta.
  const efirmaBtn = loginPage
    .locator('button:has-text("e.firma"):not(:has-text("portable")), input[type="button"][value="e.firma" i], input[type="submit"][value="e.firma" i]')
    .first();
  await efirmaBtn.waitFor({ state: 'visible', timeout: 60_000 });
  await efirmaBtn.scrollIntoViewIfNeeded();
  await efirmaBtn.click();

  const fileInputs = loginPage.locator('input[type="file"]');
  try {
    await fileInputs.first().waitFor({ state: 'attached', timeout: 10_000 });
  } catch {
    // Retry: el click sintético no disparó el handler — forzamos dispatchEvent
    await efirmaBtn.dispatchEvent('click');
    await fileInputs.first().waitFor({ state: 'attached', timeout: 30_000 });
  }

  // Upload .cer (primer input) y .key (segundo)
  await fileInputs.nth(0).setInputFiles(cerPath);
  await fileInputs.nth(1).setInputFiles(keyPath);

  // Esperar a que el cert async parsing termine (RFC auto-populado por SAT).
  let rfcPopulated = false;
  try {
    await loginPage.waitForFunction(
      () => {
        const rfc = document.getElementById('rfc') as HTMLInputElement | null;
        return rfc !== null && rfc.value.length >= 12;
      },
      null,
      { timeout: 120_000 },
    );
    rfcPopulated = true;
  } catch {
    // Fallback: si tenemos el RFC conocido, intentar llenarlo manualmente
    // (el SAT a veces no auto-popula en tiempo esperado pero acepta el envío igual).
    if (knownRfc && knownRfc.length >= 12) {
      try {
        const rfcInput = loginPage.locator('#rfc').first();
        await rfcInput.evaluate((el: HTMLElement, val: string) => {
          (el as HTMLInputElement).disabled = false;
          (el as HTMLInputElement).value = val;
          el.dispatchEvent(new Event('input', { bubbles: true }));
          el.dispatchEvent(new Event('change', { bubbles: true }));
        }, knownRfc);
        await loginPage.waitForTimeout(500);
        rfcPopulated = true;
      } catch {
        // Manual fill failed, will debug-dump below
      }
    }
  }

  if (!rfcPopulated) {
    const html = await loginPage.content();
    const { writeFileSync, mkdirSync } = await import('node:fs');
    const debugDir = '/tmp/horux-csf-debug';
    try { mkdirSync(debugDir, { recursive: true }); } catch { /* ok */ }
    writeFileSync(`${debugDir}/04c-rfc-timeout-html-${Date.now()}.html`, html);
    throw new Error('El SAT no auto-populó el RFC tras subir el certificado y no se pudo llenar manualmente');
  }

  // Password + Enviar
  await loginPage.locator('input[type="password"]').first().fill(password);
  await loginPage.locator('button:has-text("Enviar"), input[value="Enviar"]').first().click({ noWaitAfter: true });

  // Esperar a que salga del dominio de login y aterrice en el portal SAT
  await loginPage.waitForURL(
    url => url.toString().includes('wwwmat.sat.gob.mx/operacion/'),
    { timeout: 120_000 },
  );
  await loginPage.waitForLoadState('networkidle').catch(() => undefined);
  await loginPage.waitForTimeout(2000);

  const bodyText = await loginPage.locator('body').innerText().catch(() => '');
  if (/contrase[nñ]a\s+incorrecta|usuario.*no.*v[aá]lido|firma\s+inv[aá]lida/i.test(bodyText)) {
    throw new Error('FIEL inválida o contraseña incorrecta');
  }

  return { context, appPage: loginPage };
}
