const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');

const step9ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');

test('step9 observes disabled oauth timeout while waiting for localhost callback', async () => {
  const api = new Function('step9ModuleSource', `
const self = {};
let webNavListener = null;
let webNavCommittedListener = null;
let step8TabUpdatedListener = null;
let step8PendingReject = null;
let cleanupCalls = 0;
let remainingCalls = 0;
let recoveryCalls = 0;
let completePayload = null;
const callbackUrl = 'http://localhost:1455/auth/callback?code=abc&state=xyz';

const chrome = {
  webNavigation: {
    onBeforeNavigate: {
      addListener(listener) {
        webNavListener = listener;
        setTimeout(() => {
          if (typeof webNavListener === 'function') {
            webNavListener({ tabId: 123, url: callbackUrl });
          }
        }, 25);
      },
      removeListener() {},
    },
    onCommitted: {
      addListener(listener) {
        webNavCommittedListener = listener;
      },
      removeListener() {},
    },
  },
  tabs: {
    onUpdated: {
      addListener(listener) {
        step8TabUpdatedListener = listener;
      },
      removeListener() {},
    },
    async update() {},
  },
};

async function addLog() {}
function cleanupStep8NavigationListeners() {
  cleanupCalls += 1;
  webNavListener = null;
  webNavCommittedListener = null;
  step8TabUpdatedListener = null;
}
function throwIfStep8SettledOrStopped(resolved) {
  if (resolved) throw new Error('already resolved');
}
async function getTabId() { return 123; }
async function isTabAlive() { return true; }
async function ensureStep8SignupPageReady() {}
async function getOAuthFlowStepTimeoutMs(defaultTimeoutMs, options = {}) {
  if (options.actionLabel === 'OAuth localhost 回调') {
    return 1;
  }
  return defaultTimeoutMs;
}
async function getOAuthFlowRemainingMs() {
  remainingCalls += 1;
  return null;
}
async function recoverOAuthLocalhostTimeout() {
  recoveryCalls += 1;
  throw new Error('should not recover when timeout has been disabled');
}
async function waitForStep8Ready() {
  return {
    consentReady: true,
    url: 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent',
  };
}
async function triggerStep8ContentStrategy() {}
async function waitForStep8ClickEffect() {
  return { progressed: true, reason: 'url_changed', url: 'https://chatgpt.com/' };
}
function getStep8CallbackUrlFromNavigation(details, signupTabId) {
  return Number(details?.tabId) === Number(signupTabId) ? details.url : '';
}
function getStep8CallbackUrlFromTabUpdate() { return ''; }
function getStep8EffectLabel() { return 'URL 已变化'; }
async function prepareStep8DebuggerClick() { return { rect: { centerX: 10, centerY: 10 } }; }
async function clickWithDebugger() {}
async function reloadStep8ConsentPage() {}
async function reuseOrCreateTab() { return 123; }
async function sleepWithStop() {}
function setWebNavListener(listener) { webNavListener = listener; }
function getWebNavListener() { return webNavListener; }
function setWebNavCommittedListener(listener) { webNavCommittedListener = listener; }
function getWebNavCommittedListener() { return webNavCommittedListener; }
function setStep8TabUpdatedListener(listener) { step8TabUpdatedListener = listener; }
function getStep8TabUpdatedListener() { return step8TabUpdatedListener; }
function setStep8PendingReject(handler) { step8PendingReject = handler; }
async function completeStepFromBackground(step, payload) {
  completePayload = { step, payload };
}

const STEP8_CLICK_RETRY_DELAY_MS = 1;
const STEP8_READY_WAIT_TIMEOUT_MS = 5;
const STEP8_MAX_ROUNDS = 1;
const STEP8_STRATEGIES = [
  { mode: 'content', strategy: 'requestSubmit', label: 'form.requestSubmit' },
];

${step9ModuleSource}

const executor = self.MultiPageBackgroundStep9.createStep9Executor({
  addLog,
  chrome,
  cleanupStep8NavigationListeners,
  clickWithDebugger,
  completeStepFromBackground,
  ensureStep8SignupPageReady,
  getOAuthFlowRemainingMs,
  getOAuthFlowStepTimeoutMs,
  getStep8CallbackUrlFromNavigation,
  getStep8CallbackUrlFromTabUpdate,
  getStep8EffectLabel,
  getTabId,
  getWebNavCommittedListener,
  getWebNavListener,
  getStep8TabUpdatedListener,
  isTabAlive,
  prepareStep8DebuggerClick,
  recoverOAuthLocalhostTimeout,
  reloadStep8ConsentPage,
  reuseOrCreateTab,
  setStep8PendingReject,
  setStep8TabUpdatedListener,
  setWebNavCommittedListener,
  setWebNavListener,
  sleepWithStop,
  STEP8_CLICK_RETRY_DELAY_MS,
  STEP8_MAX_ROUNDS,
  STEP8_READY_WAIT_TIMEOUT_MS,
  STEP8_STRATEGIES,
  throwIfStep8SettledOrStopped,
  triggerStep8ContentStrategy,
  waitForStep8ClickEffect,
  waitForStep8Ready,
});

return {
  executeStep9: executor.executeStep9,
  snapshot() {
    return {
      cleanupCalls,
      remainingCalls,
      recoveryCalls,
      completePayload,
      hasPendingReject: Boolean(step8PendingReject),
    };
  },
};
`)(step9ModuleSource);

  await api.executeStep9({
    oauthUrl: 'https://auth.openai.com/original-oauth',
    visibleStep: 9,
  });

  const snapshot = api.snapshot();
  assert.equal(snapshot.recoveryCalls, 0);
  assert.equal(snapshot.remainingCalls >= 1, true);
  assert.equal(snapshot.cleanupCalls >= 1, true);
  assert.equal(snapshot.hasPendingReject, false);
  assert.deepEqual(snapshot.completePayload, {
    step: 9,
    payload: {
      localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
    },
  });
});

test('step9 defers callback timeout while auth security verification is still redirecting', async () => {
  const api = new Function('step9ModuleSource', `
const self = {};
let webNavListener = null;
let webNavCommittedListener = null;
let step8TabUpdatedListener = null;
let cleanupCalls = 0;
let deferCalls = 0;
let completePayload = null;
const callbackUrl = 'http://localhost:1455/auth/callback?code=abc&state=xyz';

const chrome = {
  webNavigation: {
    onBeforeNavigate: {
      addListener(listener) {
        webNavListener = listener;
        setTimeout(() => {
          if (typeof webNavListener === 'function') {
            webNavListener({ tabId: 123, url: callbackUrl });
          }
        }, 25);
      },
      removeListener() {},
    },
    onCommitted: {
      addListener(listener) {
        webNavCommittedListener = listener;
      },
      removeListener() {},
    },
  },
  tabs: {
    onUpdated: {
      addListener(listener) {
        step8TabUpdatedListener = listener;
      },
      removeListener() {},
    },
    async update() {},
  },
};

async function addLog() {}
function cleanupStep8NavigationListeners() {
  cleanupCalls += 1;
  webNavListener = null;
  webNavCommittedListener = null;
  step8TabUpdatedListener = null;
}
function throwIfStep8SettledOrStopped(resolved) {
  if (resolved) throw new Error('already resolved');
}
async function getTabId() { return 123; }
async function isTabAlive() { return true; }
async function ensureStep8SignupPageReady() {}
async function getOAuthFlowStepTimeoutMs(defaultTimeoutMs, options = {}) {
  if (options.actionLabel === 'OAuth localhost 回调') {
    return 1;
  }
  return defaultTimeoutMs;
}
async function shouldDeferStep9CallbackTimeout(details = {}) {
  deferCalls += 1;
  return Number(details.tabId) === 123;
}
async function waitForStep8Ready() {
  return {
    consentReady: true,
    url: 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent',
  };
}
async function triggerStep8ContentStrategy() {}
async function waitForStep8ClickEffect() {
  return {
    progressed: true,
    reason: 'url_changed',
    url: 'https://auth.openai.com/api/oauth/oauth2/auth?client_id=app_test',
  };
}
function getStep8CallbackUrlFromNavigation(details, signupTabId) {
  return Number(details?.tabId) === Number(signupTabId) ? details.url : '';
}
function getStep8CallbackUrlFromTabUpdate() { return ''; }
function getStep8EffectLabel() { return 'URL 已变化'; }
async function prepareStep8DebuggerClick() { return { rect: { centerX: 10, centerY: 10 } }; }
async function clickWithDebugger() {}
async function reloadStep8ConsentPage() {}
async function reuseOrCreateTab() { return 123; }
async function sleepWithStop() {}
function setWebNavListener(listener) { webNavListener = listener; }
function getWebNavListener() { return webNavListener; }
function setWebNavCommittedListener(listener) { webNavCommittedListener = listener; }
function getWebNavCommittedListener() { return webNavCommittedListener; }
function setStep8TabUpdatedListener(listener) { step8TabUpdatedListener = listener; }
function getStep8TabUpdatedListener() { return step8TabUpdatedListener; }
function setStep8PendingReject() {}
async function completeStepFromBackground(step, payload) {
  completePayload = { step, payload };
}

const STEP8_CLICK_RETRY_DELAY_MS = 1;
const STEP8_READY_WAIT_TIMEOUT_MS = 5;
const STEP8_MAX_ROUNDS = 1;
const STEP8_STRATEGIES = [
  { mode: 'content', strategy: 'requestSubmit', label: 'form.requestSubmit' },
];

${step9ModuleSource}

const executor = self.MultiPageBackgroundStep9.createStep9Executor({
  addLog,
  chrome,
  cleanupStep8NavigationListeners,
  clickWithDebugger,
  completeStepFromBackground,
  ensureStep8SignupPageReady,
  getOAuthFlowStepTimeoutMs,
  getStep8CallbackUrlFromNavigation,
  getStep8CallbackUrlFromTabUpdate,
  getStep8EffectLabel,
  getTabId,
  getWebNavCommittedListener,
  getWebNavListener,
  getStep8TabUpdatedListener,
  isTabAlive,
  prepareStep8DebuggerClick,
  reloadStep8ConsentPage,
  reuseOrCreateTab,
  setStep8PendingReject,
  setStep8TabUpdatedListener,
  setWebNavCommittedListener,
  setWebNavListener,
  shouldDeferStep9CallbackTimeout,
  sleepWithStop,
  STEP8_CLICK_RETRY_DELAY_MS,
  STEP8_MAX_ROUNDS,
  STEP8_READY_WAIT_TIMEOUT_MS,
  STEP8_STRATEGIES,
  throwIfStep8SettledOrStopped,
  triggerStep8ContentStrategy,
  waitForStep8ClickEffect,
  waitForStep8Ready,
});

return {
  executeStep9: executor.executeStep9,
  snapshot() {
    return {
      cleanupCalls,
      deferCalls,
      completePayload,
    };
  },
};
`)(step9ModuleSource);

  await api.executeStep9({
    oauthUrl: 'https://auth.openai.com/original-oauth',
    visibleStep: 12,
  });

  const snapshot = api.snapshot();
  assert.equal(snapshot.deferCalls >= 1, true);
  assert.equal(snapshot.cleanupCalls >= 1, true);
  assert.deepEqual(snapshot.completePayload, {
    step: 12,
    payload: {
      localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
    },
  });
});
