const { exec, spawn } = require("child_process");

// En Windows probamos varios metodos en cadena porque "start" a veces no
// abre nada (segun politica de asociacion de protocolos, o cuando cmd.exe
// interpreta mal la URL) sin devolver ningun error que podamos loguear.
// rundll32 con url.dll es el mecanismo que usa el propio Windows Explorer
// para abrir el navegador predeterminado y es mas fiable que "start".
function openBrowserWindows(url, onDone) {
  const attempts = [
    () => new Promise((resolve, reject) => {
      const p = spawn("rundll32.exe", ["url.dll,FileProtocolHandler", url], { detached: true, stdio: "ignore" });
      p.on("error", reject);
      p.unref();
      setTimeout(resolve, 300);
    }),
    () => new Promise((resolve, reject) => {
      exec(`cmd /c start "" "${url}"`, (err) => err ? reject(err) : resolve());
    }),
    () => new Promise((resolve, reject) => {
      exec(`explorer.exe "${url}"`, (err) => {
        // explorer.exe casi siempre devuelve codigo de salida 1 aunque abra
        // bien la URL, asi que no tratamos su "error" como fallo real.
        resolve();
      });
    }),
  ];

  let i = 0;
  function tryNext() {
    if (i >= attempts.length) { onDone(new Error("todos los metodos fallaron")); return; }
    const method = i;
    attempts[i++]()
      .then(() => { console.log(`[auth] navegador abierto (metodo ${method + 1})`); onDone(null); })
      .catch((err) => { console.warn(`[auth] metodo ${method + 1} fallo: ${err.message}`); tryNext(); });
  }
  tryNext();
}

function openBrowser(url) {
  const platform = process.platform;

  if (platform === "win32") {
    openBrowserWindows(url, (err) => {
      if (err) {
        console.warn(`[auth] No se pudo abrir el navegador automaticamente.`);
        console.warn(`[auth] Ábrelo manualmente en: ${url}`);
      }
    });
    return;
  }

  const cmd = platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`;
  exec(cmd, (err) => {
    if (err) {
      console.warn(`[auth] No se pudo abrir el navegador automaticamente.`);
      console.warn(`[auth] Ábrelo manualmente en: ${url}`);
    }
  });
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Delega el login a una pagina web (MetaMask o Immutable Passport) usando
 * el mismo patron "device code" que usan herramientas como `gh auth login`
 * o `docker login`: pedimos un codigo, abrimos el navegador en una pagina
 * de nuestro propio dominio (no localhost, asi evitamos los problemas de
 * "mixed content"/redes privadas de los navegadores modernos), y vamos
 * preguntando a la API si ya se completo.
 */
async function fetchWithTimeout(url, opts, ms = 10000) {
  const ctrl = new AbortController();
  const tid = setTimeout(() => ctrl.abort(), ms);
  try {
    const res = await fetch(url, { ...opts, signal: ctrl.signal });
    clearTimeout(tid);
    return res;
  } catch (err) {
    clearTimeout(tid);
    throw err;
  }
}

async function browserLogin(apiUrl) {
  const startRes = await fetchWithTimeout(`${apiUrl}/auth/device/start`, { method: "POST" });
  const startData = await startRes.json();
  if (!startData.success) {
    throw new Error(startData.error || "no se pudo iniciar el login por navegador");
  }

  const deviceCode = startData.device_code;
  const loginUrl = `${apiUrl}/setup/connect.html?device_code=${deviceCode}`;

  console.log("[auth] Abriendo el navegador para conectar tu wallet...");
  console.log(`[auth] Si no se abre solo, entra manualmente a: ${loginUrl}`);
  openBrowser(loginUrl);

  const deadline = Date.now() + startData.expires_in * 1000;
  while (Date.now() < deadline) {
    await sleep(2000);

    const pollRes = await fetchWithTimeout(`${apiUrl}/auth/device/poll?device_code=${deviceCode}`, {});
    const pollData = await pollRes.json();
    if (!pollData.success) {
      throw new Error(pollData.error || "el codigo de conexion es invalido o expiro");
    }
    if (pollData.status === "completed") {
      return { token: pollData.token, wallet: pollData.wallet, expiresAt: pollData.expires_at };
    }
  }

  throw new Error("se agoto el tiempo de espera, no se completo el login en el navegador");
}

module.exports = { browserLogin };
