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

const source = fs.readFileSync('content/mail-163.js', 'utf8');

function extractFunction(name) {
  const markers = [`async function ${name}(`, `function ${name}(`];
  const start = markers
    .map((marker) => source.indexOf(marker))
    .find((index) => index >= 0);
  if (start < 0) {
    throw new Error(`missing function ${name}`);
  }

  let parenDepth = 0;
  let signatureEnded = false;
  let braceStart = -1;
  for (let i = start; i < source.length; i += 1) {
    const ch = source[i];
    if (ch === '(') {
      parenDepth += 1;
    } else if (ch === ')') {
      parenDepth -= 1;
      if (parenDepth === 0) {
        signatureEnded = true;
      }
    } else if (ch === '{' && signatureEnded) {
      braceStart = i;
      break;
    }
  }
  if (braceStart < 0) {
    throw new Error(`missing body for function ${name}`);
  }

  let depth = 0;
  let end = braceStart;
  for (; end < source.length; end += 1) {
    const ch = source[end];
    if (ch === '{') depth += 1;
    if (ch === '}') {
      depth -= 1;
      if (depth === 0) {
        end += 1;
        break;
      }
    }
  }

  return source.slice(start, end);
}

test('findMailItems falls back to visible aria-label mail rows when legacy selector is missing', () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('isVisibleNode'),
    extractFunction('isLikelyMailItemNode'),
    extractFunction('findMailItems'),
  ].join('\n');

  const api = new Function(`
const mailRow = {
  hidden: false,
  textContent: 'Your temporary ChatGPT verification code 911113',
  getAttribute(name) {
    if (name === 'aria-label') return 'Your temporary ChatGPT verification code 911113 发件人 OpenAI';
    return '';
  },
  getBoundingClientRect() {
    return { width: 600, height: 48 };
  },
  matches() {
    return false;
  },
  querySelector() {
    return null;
  },
};

const document = {
  querySelectorAll(selector) {
    if (selector === 'div[sign="letter"]') return [];
    if (selector === '[role="option"][aria-label]') return [mailRow];
    return [];
  },
};

const window = {
  getComputedStyle() {
    return { display: 'block', visibility: 'visible' };
  },
};

${bundle}

return { findMailItems };
`)();

  const rows = api.findMailItems();
  assert.equal(rows.length, 1);
});

test('getNetEaseMailLabel returns the active NetEase mailbox brand', () => {
  const bundle = [
    extractFunction('getNetEaseMailLabel'),
  ].join('\n');

  const api = new Function(`
${bundle}
return { getNetEaseMailLabel };
`)();

  assert.equal(api.getNetEaseMailLabel('mail.126.com'), '126 邮箱');
  assert.equal(api.getNetEaseMailLabel('app.mail.126.com'), '126 邮箱');
  assert.equal(api.getNetEaseMailLabel('webmail.vip.163.com'), '163 VIP 邮箱');
  assert.equal(api.getNetEaseMailLabel('mail.163.com'), '163 邮箱');
  assert.equal(api.getNetEaseMailLabel('example.com'), '163 邮箱');
});

test('getMailTimestamp parses visible hh:mm text even when no title attribute exists', () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('parseMail163Timestamp'),
    extractFunction('isLikelyMailTimestampText'),
    extractFunction('collectMailTimestampCandidates'),
    extractFunction('getMailTimestamp'),
  ].join('\n');

  const timestamp = new Function(`
const item = {
  getAttribute() {
    return '';
  },
  querySelectorAll(selector) {
    if (selector === '.e00, [title], [aria-label], time, [class*="time"], [class*="date"]') {
      return [];
    }
    if (selector === 'span, div, td, strong, b') {
      return [
        {
          textContent: '22:22',
          getAttribute() {
            return '';
          },
        },
      ];
    }
    return [];
  },
};

${bundle}

return getMailTimestamp(item);
`)();

  const now = new Date();
  const expected = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 22, 22, 0, 0).getTime();
  assert.equal(timestamp, expected);
});

test('readOpenedMailText prefers opened body content that contains the verification code', () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('collectOpenedMailTextCandidates'),
    extractFunction('selectOpenedMailTextCandidate'),
    extractFunction('readOpenedMailText'),
    extractFunction('extractVerificationCode'),
  ].join('\n');

  const text = new Function(`
const item = {
  querySelectorAll() {
    return [];
  },
};

function getMailSubjectText() {
  return 'Your temporary ChatGPT login code';
}

function getMailSenderText() {
  return 'OpenAI';
}

const document = {
  querySelectorAll(selector) {
    if (selector === 'iframe') {
      return [];
    }
    return [
      { innerText: 'Your temporary ChatGPT login code', textContent: 'Your temporary ChatGPT login code' },
      { innerText: 'Enter this temporary verification code to continue: 214203', textContent: 'Enter this temporary verification code to continue: 214203' },
    ];
  },
  body: {
    innerText: 'fallback body',
    textContent: 'fallback body',
  },
};

${bundle}

return readOpenedMailText(item);
`)();

  assert.match(text, /214203/);
});

test('openMailAndGetMessageText reads opened body text and returns to inbox', async () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('collectOpenedMailTextCandidates'),
    extractFunction('selectOpenedMailTextCandidate'),
    extractFunction('readOpenedMailText'),
    extractFunction('returnToInbox'),
    extractFunction('openMailAndGetMessageText'),
    extractFunction('extractVerificationCode'),
  ].join('\n');

  const api = new Function(`
let inInbox = true;
let clickCount = 0;
const mailItem = {
  click() {
    clickCount += 1;
    inInbox = false;
  },
};
const inboxLink = {
  click() {
    inInbox = true;
  },
};

function getMailSubjectText() {
  return 'Your temporary ChatGPT login code';
}

function getMailSenderText() {
  return 'OpenAI';
}

const document = {
  querySelector(selector) {
    if (selector === '.nui-tree-item-text[title="收件箱"], [title="收件箱"]') return inboxLink;
    return null;
  },
  querySelectorAll(selector) {
    if (selector === 'iframe') {
      return [];
    }
    return inInbox ? [] : [{ innerText: 'Enter this temporary verification code to continue: 214203', textContent: 'Enter this temporary verification code to continue: 214203' }];
  },
  body: {
    innerText: '',
    textContent: '',
  },
};

function findMailItems() {
  return inInbox ? [mailItem] : [];
}

async function sleep() {}

${bundle}

return {
  openMailAndGetMessageText,
  getClickCount: () => clickCount,
  isInInbox: () => inInbox,
  mailItem,
};
`)();

  const text = await api.openMailAndGetMessageText(api.mailItem);
  assert.match(text, /214203/);
  assert.equal(api.getClickCount(), 1);
  assert.equal(api.isInInbox(), true);
});

test('openMailAndGetMessageText ignores stale pre-open text that contains an old code', async () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('collectOpenedMailTextCandidates'),
    extractFunction('selectOpenedMailTextCandidate'),
    extractFunction('readOpenedMailText'),
    extractFunction('returnToInbox'),
    extractFunction('openMailAndGetMessageText'),
    extractFunction('extractVerificationCode'),
  ].join('\n');

  const api = new Function(`
let stage = 'before';
const oldText = 'OpenAI Your temporary ChatGPT login code. Ignore this old code 111111. '.repeat(10);
const newText = 'OpenAI Your temporary ChatGPT login code. Your new code is 222222.';
const mailItem = {
  click() {
    stage = 'after';
  },
};
const inboxLink = {
  click() {
    stage = 'done';
  },
};

function getMailSubjectText() {
  return 'Your temporary ChatGPT login code';
}

function getMailSenderText() {
  return 'OpenAI';
}

const document = {
  querySelector(selector) {
    if (selector === '.nui-tree-item-text[title="收件箱"], [title="收件箱"]') return inboxLink;
    return null;
  },
  querySelectorAll(selector) {
    if (selector === 'iframe') {
      return [];
    }
    if (stage === 'before') {
      return [{ innerText: oldText, textContent: oldText }];
    }
    if (stage === 'after') {
      return [
        { innerText: oldText, textContent: oldText },
        { innerText: newText, textContent: newText },
      ];
    }
    return [];
  },
  body: {
    innerText: '',
    textContent: '',
  },
};

function findMailItems() {
  return stage === 'done' ? [mailItem] : [];
}

async function sleep() {}

${bundle}

return { openMailAndGetMessageText, mailItem };
`)();

  const text = await api.openMailAndGetMessageText(api.mailItem);
  assert.match(text, /222222/);
  assert.doesNotMatch(text, /111111/);
});

test('extractVerificationCode matches the new suspicious log-in mail body', () => {
  const bundle = [
    extractFunction('extractVerificationCode'),
  ].join('\n');

  const api = new Function(`
${bundle}
return { extractVerificationCode };
`)();

  const bodyText = 'ChatGPT Log-in Code\nWe noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219';
  assert.equal(api.extractVerificationCode(bodyText), '982219');
});

test('handlePollEmail ignores same-minute old snapshot mail before fallback', async () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('normalizeMinuteTimestamp'),
    extractFunction('getNetEaseMailLabel'),
    extractFunction('handlePollEmail'),
  ].join('\n');

  const api = new Function(`
const location = { hostname: 'mail.126.com' };
let currentItems = [{ id: 'old-mail' }];
const seenCodes = new Set();

function findMailItems() {
  return currentItems;
}

function getCurrentMailIds(items = []) {
  return new Set((items.length ? items : currentItems).map((item) => item.id));
}

function getMailItemId(item) {
  return item.id;
}

function getMailTimestamp() {
  return new Date(2026, 3, 22, 22, 22, 0, 0).getTime();
}

function getMailSenderText() {
  return 'OpenAI';
}

function getMailSubjectText() {
  return 'Your temporary ChatGPT verification code';
}

function getMailRowText() {
  return 'Your temporary ChatGPT verification code 911113 发件人 OpenAI';
}

function extractVerificationCode() {
  return '911113';
}

async function waitForElement() {
  return { click() {} };
}
async function refreshInbox() {}
async function sleep() {}
function log() {}
function persistSeenCodes() {}
function scheduleEmailCleanup() {}

${bundle}

return { handlePollEmail };
`)();

  await assert.rejects(
    () => api.handlePollEmail(4, {
      senderFilters: ['openai'],
      subjectFilters: ['verification'],
      maxAttempts: 1,
      intervalMs: 1,
      filterAfterTimestamp: new Date(2026, 3, 22, 22, 22, 40, 0).getTime(),
    }),
    /未在 126 邮箱中找到新的匹配邮件/
  );
});

test('handlePollEmail accepts a new same-minute mail that appears after the snapshot', async () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('normalizeMinuteTimestamp'),
    extractFunction('getNetEaseMailLabel'),
    extractFunction('handlePollEmail'),
  ].join('\n');

  const api = new Function(`
const oldMail = {
  id: 'old-mail',
  getAttribute(name) {
    if (name === 'aria-label') return 'Old verification mail 111111 发件人 OpenAI';
    return '';
  },
};
const newMail = {
  id: 'new-mail',
  getAttribute(name) {
    if (name === 'aria-label') return 'Your temporary ChatGPT verification code 654321 发件人 OpenAI';
    return '';
  },
};
let refreshCount = 0;
let currentItems = [oldMail];
const seenCodes = new Set();

function findMailItems() {
  return currentItems;
}

function getCurrentMailIds(items = []) {
  return new Set((items.length ? items : currentItems).map((item) => item.id));
}

function getMailItemId(item) {
  return item.id;
}

function getMailTimestamp() {
  return new Date(2026, 3, 22, 22, 22, 0, 0).getTime();
}

function getMailSenderText() {
  return 'OpenAI';
}

function getMailSubjectText(item) {
  return item.id === 'new-mail' ? 'Your temporary ChatGPT verification code' : 'Old verification mail';
}

function getMailRowText(item) {
  return item.id === 'new-mail'
    ? 'Your temporary ChatGPT verification code 654321 发件人 OpenAI'
    : 'Old verification mail 111111 发件人 OpenAI';
}

function extractVerificationCode(text) {
  const match = String(text || '').match(/(\\d{6})/);
  return match ? match[1] : null;
}

async function waitForElement() {
  return { click() {} };
}
async function refreshInbox() {
  refreshCount += 1;
  if (refreshCount >= 1) {
    currentItems = [oldMail, newMail];
  }
}
async function sleep() {}
function log() {}
function persistSeenCodes() {}
function scheduleEmailCleanup() {}

${bundle}

return { handlePollEmail };
`)();

  const result = await api.handlePollEmail(4, {
    senderFilters: ['openai'],
    subjectFilters: ['verification'],
    maxAttempts: 2,
    intervalMs: 1,
    filterAfterTimestamp: new Date(2026, 3, 22, 22, 22, 40, 0).getTime(),
  });

  assert.equal(result.code, '654321');
  assert.equal(result.mailId, 'new-mail');
});

test('handlePollEmail falls back to row text when the subject node is missing', async () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('normalizeMinuteTimestamp'),
    extractFunction('getNetEaseMailLabel'),
    extractFunction('handlePollEmail'),
  ].join('\n');

  const api = new Function(`
const matchingMail = {
  id: 'mail-1',
  getAttribute(name) {
    if (name === 'aria-label') return 'OpenAI Your temporary ChatGPT verification code 123456';
    return '';
  },
};
const seenCodes = new Set();

function findMailItems() {
  return [matchingMail];
}

function getCurrentMailIds() {
  return new Set();
}

function getMailItemId(item) {
  return item.id;
}

function getMailTimestamp() {
  return new Date(2026, 3, 22, 22, 22, 0, 0).getTime();
}

function getMailSenderText() {
  return '';
}

function getMailSubjectText() {
  return '';
}

function getMailRowText() {
  return 'OpenAI Your temporary ChatGPT verification code 123456';
}

function extractVerificationCode(text) {
  const match = String(text || '').match(/(\\d{6})/);
  return match ? match[1] : null;
}

async function waitForElement() {
  return { click() {} };
}
async function refreshInbox() {}
async function sleep() {}
function log() {}
function persistSeenCodes() {}
function scheduleEmailCleanup() {}
async function openMailAndGetMessageText() {
  return '';
}

${bundle}

return { handlePollEmail };
`)();

  const result = await api.handlePollEmail(8, {
    senderFilters: ['openai'],
    subjectFilters: ['verification'],
    maxAttempts: 1,
    intervalMs: 1,
    filterAfterTimestamp: 0,
  });

  assert.equal(result.code, '123456');
  assert.equal(result.mailId, 'mail-1');
});

test('handlePollEmail opens matching mail body when preview has no code', async () => {
  const bundle = [
    extractFunction('normalizeText'),
    extractFunction('normalizeMinuteTimestamp'),
    extractFunction('getNetEaseMailLabel'),
    extractFunction('handlePollEmail'),
  ].join('\n');

  const api = new Function(`
const matchingMail = {
  id: 'mail-body-1',
  getAttribute(name) {
    if (name === 'aria-label') return 'OpenAI Your temporary ChatGPT login code';
    return '';
  },
};
const seenCodes = new Set();
let openedCount = 0;

function findMailItems() {
  return [matchingMail];
}

function getCurrentMailIds() {
  return new Set();
}

function getMailItemId(item) {
  return item.id;
}

function getMailTimestamp() {
  return new Date(2026, 3, 22, 22, 49, 0, 0).getTime();
}

function getMailSenderText() {
  return 'OpenAI';
}

function getMailSubjectText() {
  return 'Your temporary ChatGPT login code';
}

function getMailRowText() {
  return 'OpenAI Your temporary ChatGPT login code';
}

function extractVerificationCode(text) {
  const match = String(text || '').match(/(\\d{6})/);
  return match ? match[1] : null;
}

async function waitForElement() {
  return { click() {} };
}
async function refreshInbox() {}
async function sleep() {}
function log() {}
function persistSeenCodes() {}
function scheduleEmailCleanup() {}
async function openMailAndGetMessageText() {
  openedCount += 1;
  return 'Enter this temporary verification code to continue: 214203';
}

${bundle}

return { handlePollEmail, getOpenedCount: () => openedCount };
`)();

  const result = await api.handlePollEmail(8, {
    senderFilters: ['openai'],
    subjectFilters: ['verification', 'login'],
    maxAttempts: 1,
    intervalMs: 1,
    filterAfterTimestamp: 0,
  });

  assert.equal(result.code, '214203');
  assert.equal(result.mailId, 'mail-body-1');
  assert.equal(api.getOpenedCount(), 1);
});
