import { Page, expect } from "@playwright/test";
import * as path from "path";

export interface LoginOptions {
  url: string;
  user: string;
  password: string;
  recoveryKey: string;
  /** Directorio donde guardar screenshots de debug (opcional) */
  screenshotsDir?: string;
}

async function screenshot(page: Page, dir: string | undefined, name: string) {
  if (!dir) return;
  const filePath = path.join(dir, name);
  await page.screenshot({ path: filePath, fullPage: true });
  console.log(`[login] Screenshot: ${name}`);
}

/**
 * Ejecuta el flujo completo de login en Element Web:
 * 1. Navegar a Element Web
 * 2. Click "Sign in"
 * 3. Ingresar usuario y contraseña
 * 4. Detectar errores (M_LIMIT_EXCEEDED, etc.) y reintentar
 * 5. Manejar verificacion de dispositivo con recovery key
 * 6. Verificar login exitoso (lista de rooms visible)
 */
export async function loginToElement(page: Page, opts: LoginOptions) {
  const ssDir = opts.screenshotsDir;

  console.log(`[login] Navegando a ${opts.url}`);
  await page.goto(opts.url);
  await screenshot(page, ssDir, "01-element-loaded.png");

  // Esperar a que cargue Element y aparezca el boton de login
  console.log("[login] Buscando boton 'Sign in'...");
  const signInLink = page.getByRole("link", { name: "Sign in" });
  const hasSignIn = await signInLink
    .waitFor({ state: "visible", timeout: 15_000 })
    .then(() => true)
    .catch(() => false);

  if (hasSignIn) {
    console.log("[login] Click en 'Sign in'");
    await signInLink.click();
  } else {
    console.log(
      "[login] No se encontro 'Sign in' link — puede que ya estemos en la pagina de login"
    );
    await screenshot(page, ssDir, "01b-no-signin-link.png");
  }

  await screenshot(page, ssDir, "02-signin-page.png");

  // Intentar login con reintentos para M_LIMIT_EXCEEDED
  const maxRetries = 3;
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    console.log(`[login] Intento de login ${attempt}/${maxRetries}`);

    // Rellenar credenciales
    console.log(`[login] Rellenando credenciales para: ${opts.user}`);
    const usernameField = page.getByRole("textbox", { name: "Username" });
    const hasUsername = await usernameField
      .waitFor({ state: "visible", timeout: 10_000 })
      .then(() => true)
      .catch(() => false);

    if (!hasUsername) {
      // Puede que ya hayamos pasado la pantalla de login (sesion activa)
      console.log("[login] Campo Username no encontrado — verificando si ya hay sesion...");
      await screenshot(page, ssDir, `ERROR-no-username-attempt${attempt}.png`);

      const roomsTree = page.locator('[role="tree"][aria-label="Rooms"], .mx_RoomList, .mx_LeftPanel_roomListContainer, .mx_RoomTile').first();
      const alreadyLoggedIn = await roomsTree
        .waitFor({ state: "visible", timeout: 5_000 })
        .then(() => true)
        .catch(() => false);

      if (alreadyLoggedIn) {
        console.log("[login] Ya hay sesion activa, saltando login");
        return;
      }
      throw new Error("Campo Username no encontrado y no hay sesion activa");
    }

    await usernameField.fill(opts.user);
    await page.getByRole("textbox", { name: "Password" }).fill(opts.password);
    await screenshot(page, ssDir, `02b-credentials-filled-attempt${attempt}.png`);

    console.log("[login] Click en 'Sign in' button");
    await page.getByRole("button", { name: "Sign in" }).click();

    await screenshot(page, ssDir, `03-after-signin-click-attempt${attempt}.png`);

    // Esperar resultado: o bien aparece el verify prompt / rooms,
    // o bien aparece un error
    console.log("[login] Esperando resultado del login...");
    const result = await waitForLoginResult(page);

    if (result === "success") {
      console.log("[login] Login exitoso (rooms visibles o verify prompt)");
      break;
    }

    if (result === "rate_limited") {
      const waitSecs = 10 * attempt; // 10s, 20s, 30s
      console.log(
        `[login] Rate limited (M_LIMIT_EXCEEDED). Esperando ${waitSecs}s antes de reintentar...`
      );
      await screenshot(page, ssDir, `ERROR-rate-limited-attempt${attempt}.png`);

      if (attempt === maxRetries) {
        throw new Error(
          `Login fallido despues de ${maxRetries} intentos: M_LIMIT_EXCEEDED. ` +
            "El homeserver esta limitando los intentos de login. Esperar unos minutos."
        );
      }

      await page.waitForTimeout(waitSecs * 1000);
      // Recargar pagina para limpiar estado
      await page.goto(opts.url + "/#/login");
      await page.waitForTimeout(2_000);
      continue;
    }

    // Otro error
    console.error(`[login] Error de login: ${result}`);
    await screenshot(page, ssDir, `ERROR-login-attempt${attempt}.png`);

    if (attempt === maxRetries) {
      throw new Error(`Login fallido despues de ${maxRetries} intentos: ${result}`);
    }

    // Esperar un poco antes de reintentar
    await page.waitForTimeout(3_000);
  }

  // Manejar cross-signing: verificar con recovery key
  console.log("[login] Esperando prompt de cross-signing...");
  await handleCrossSigning(page, opts.recoveryKey, ssDir);

  // Verificar login exitoso: rooms visibles en el sidebar
  console.log("[login] Verificando que rooms sidebar es visible...");
  const roomsVisible = await page
    .locator('[role="tree"][aria-label="Rooms"]')
    .waitFor({ state: "visible", timeout: 30_000 })
    .then(() => true)
    .catch(() => false);

  if (!roomsVisible) {
    await screenshot(page, ssDir, "ERROR-no-rooms-after-login.png");
    throw new Error("Rooms sidebar no visible despues del login completo");
  }

  await screenshot(page, ssDir, "04-rooms-visible.png");
  console.log("[login] Login completado exitosamente");
}

/**
 * Espera el resultado del login: exito, rate_limited, u otro error.
 * Retorna "success" si el login progresó (verify prompt o rooms visibles),
 * "rate_limited" si hay M_LIMIT_EXCEEDED, o el texto del error.
 */
async function waitForLoginResult(page: Page): Promise<string> {
  const timeout = 20_000;
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    // Verificar si hay un error visible en la pagina de login
    const errorAlert = page.locator('[role="alert"], .mx_Login_error, .mx_ErrorMessage');
    const errorCount = await errorAlert.count();
    if (errorCount > 0) {
      const errorText = await errorAlert.first().textContent();
      if (errorText) {
        console.log(`[login] Error detectado: "${errorText}"`);
        if (errorText.includes("M_LIMIT_EXCEEDED") || errorText.includes("rate")) {
          return "rate_limited";
        }
        return errorText;
      }
    }

    // Verificar si hay texto de error generico en la pagina
    const pageText = await page.locator(".mx_Login_header, .mx_AuthBody").textContent().catch(() => "");
    if (pageText && pageText.includes("M_LIMIT_EXCEEDED")) {
      return "rate_limited";
    }

    // Verificar si el login progresó (verify prompt)
    const verifyButton = page.getByRole("button", {
      name: /verify with security key|use security key/i,
    });
    if (await verifyButton.isVisible().catch(() => false)) {
      return "success";
    }

    // Verificar si ya estamos en el home (rooms visibles)
    const roomsTree = page.locator('[role="tree"][aria-label="Rooms"], .mx_RoomList, .mx_LeftPanel_roomListContainer, .mx_RoomTile').first();
    if (await roomsTree.isVisible().catch(() => false)) {
      return "success";
    }

    // Verificar si hay un dialogo de "Verify this device" u otro post-login
    const verifyDialog = page.locator('.mx_AuthPage_modal, .mx_Dialog, [role="dialog"]');
    if (await verifyDialog.isVisible().catch(() => false)) {
      return "success";
    }

    await page.waitForTimeout(500);
  }

  // Si despues del timeout seguimos en la pagina de Sign In, es error
  const stillOnLogin = await page.locator('button:has-text("Sign in")').isVisible().catch(() => false);
  if (stillOnLogin) {
    // Capturar cualquier texto de error visible
    const bodyText = await page.locator(".mx_AuthBody").textContent().catch(() => "");
    if (bodyText && bodyText.includes("M_LIMIT_EXCEEDED")) {
      return "rate_limited";
    }
    return `Timeout: aun en pagina de login. Body: ${bodyText?.substring(0, 200)}`;
  }

  return "success";
}

/**
 * Maneja el prompt de verificacion de dispositivo despues del login.
 * Element puede mostrar un dialogo pidiendo verificar el dispositivo
 * via otro dispositivo o via recovery key.
 */
async function handleCrossSigning(
  page: Page,
  recoveryKey: string,
  ssDir?: string
) {
  // Element muestra un dialogo de verificacion de dispositivo.
  // Intentar usar "Verify with Security Key" si aparece.
  const verifyButton = page.getByRole("button", {
    name: /verify with security key|use security key/i,
  });

  // El dialogo puede tardar en aparecer tras el login
  const hasVerifyPrompt = await verifyButton
    .waitFor({ state: "visible", timeout: 15_000 })
    .then(() => true)
    .catch(() => false);

  if (!hasVerifyPrompt) {
    console.log(
      "[login] No hubo prompt de verificacion — sesion ya verificada o login directo"
    );
    await screenshot(page, ssDir, "03b-no-verify-prompt.png");
    return;
  }

  console.log("[login] Prompt de verificacion detectado, clickeando...");
  await screenshot(page, ssDir, "03c-verify-prompt.png");
  await verifyButton.click();

  // Ingresar recovery key en el campo de texto
  console.log("[login] Ingresando recovery key...");
  const keyInput = page.getByRole("textbox");
  await keyInput.fill(recoveryKey);
  await screenshot(page, ssDir, "03d-recovery-key-filled.png");

  // Confirmar
  console.log("[login] Click en 'Continue'...");
  await page.getByRole("button", { name: /continue/i }).click();

  // Esperar a que se complete la verificacion (el dialogo desaparece)
  console.log("[login] Esperando boton 'Done'...");
  const doneButton = page.getByRole("button", { name: /done/i });
  const hasDone = await doneButton
    .waitFor({ state: "visible", timeout: 15_000 })
    .then(() => true)
    .catch(() => false);

  if (hasDone) {
    await doneButton.click();
    console.log("[login] Verificacion completada (Done)");
  } else {
    console.log("[login] No se encontro 'Done' — verificacion puede haber terminado automaticamente");
    await screenshot(page, ssDir, "03e-no-done-button.png");
  }
}
