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

const source = fs.readFileSync('background.js', 'utf8');
const messageRouterSource = fs.readFileSync('background/message-router.js', 'utf8');
const messageRouterApi = new Function(
  'self',
  `${messageRouterSource}; return self.MultiPageBackgroundMessageRouter;`
)({});

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);
}

function createLuckmailPlatformVerifyRouter({
  state,
  stepKeyByStep = { 10: 'platform-verify' },
} = {}) {
  let clearedOptions = null;
  let usedMarker = null;
  const logs = [];
  const router = messageRouterApi.createMessageRouter({
    addLog: async (message, level) => {
      logs.push({ message, level });
    },
    buildLocalhostCleanupPrefix: () => '',
    clearLuckmailRuntimeState: async (options) => {
      clearedOptions = options;
    },
    closeLocalhostCallbackTabs: async () => {},
    closeTabsByUrlPrefix: async () => {},
    finalizeIcloudAliasAfterSuccessfulFlow: async () => {},
    finalizePhoneActivationAfterSuccessfulFlow: async () => {},
    getCurrentLuckmailPurchase: (latestState) => latestState.currentLuckmailPurchase,
    getState: async () => state,
    getStepDefinitionForState: (step) => ({ id: step, key: stepKeyByStep[step] || '' }),
    isHotmailProvider: () => false,
    isLocalhostOAuthCallbackUrl: () => true,
    isLuckmailProvider: (latestState) => latestState.mailProvider === 'luckmail-api',
    patchHotmailAccount: async () => {},
    setLuckmailPurchaseUsedState: async (purchaseId, used) => {
      usedMarker = { purchaseId, used };
    },
  });
  return {
    router,
    snapshot() {
      return { clearedOptions, usedMarker, logs };
    },
  };
}

test('ensureLuckmailPurchaseForFlow buys openai mailbox and defaults email type to ms_graph', async () => {
  const bundle = [
    extractFunction('getLuckmailSessionConfig'),
    extractFunction('getCurrentLuckmailPurchase'),
    extractFunction('ensureLuckmailPurchaseForFlow'),
  ].join('\n');

  const factory = new Function('initialState', `
let currentState = { ...initialState };
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
const purchaseCalls = [];
const activateCalls = [];

function normalizeLuckmailBaseUrl(value) {
  return String(value || '').trim() || 'https://mails.luckyous.com';
}
function normalizeLuckmailEmailType(value) {
  return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
    ? String(value || '').trim()
    : 'ms_graph';
}
function normalizeLuckmailPurchase(value) {
  return value;
}
function normalizeLuckmailPurchases(value) {
  return value.purchases || [];
}
async function getState() {
  return currentState;
}
function createLuckmailClient() {
  return {
    user: {
      async purchaseEmails(projectCode, quantity, options) {
        purchaseCalls.push({ projectCode, quantity, options });
        return {
          purchases: [{ id: 15, email_address: 'demo@outlook.com', token: 'tok-1' }],
        };
      },
    },
  };
}
async function findReusableLuckmailPurchaseForFlow() {
  return null;
}
async function activateLuckmailPurchaseForFlow(state, client, purchase, options) {
  activateCalls.push({ state, purchase, options });
  currentState.currentLuckmailPurchase = purchase;
  currentState.email = purchase.email_address;
  return purchase;
}

${bundle}

return {
  ensureLuckmailPurchaseForFlow,
  snapshot() {
    return { currentState, purchaseCalls, activateCalls };
  },
};
`);

  const api = factory({
    luckmailApiKey: 'sk-test',
    luckmailBaseUrl: '',
    luckmailEmailType: '',
    luckmailDomain: '',
    currentLuckmailPurchase: null,
    email: null,
  });

  const purchase = await api.ensureLuckmailPurchaseForFlow();
  const snapshot = api.snapshot();

  assert.equal(purchase.email_address, 'demo@outlook.com');
  assert.deepStrictEqual(snapshot.purchaseCalls, [{
    projectCode: 'openai',
    quantity: 1,
    options: {
      emailType: 'ms_graph',
      domain: undefined,
    },
  }]);
  assert.equal(snapshot.activateCalls[0].options.initializeCursor, false);
  assert.equal(snapshot.currentState.email, 'demo@outlook.com');
});

test('ensureLuckmailPurchaseForFlow reuses reusable openai mailbox before buying a new one', async () => {
  const bundle = [
    extractFunction('getLuckmailSessionConfig'),
    extractFunction('getCurrentLuckmailPurchase'),
    extractFunction('ensureLuckmailPurchaseForFlow'),
  ].join('\n');

  const factory = new Function('initialState', `
let currentState = { ...initialState };
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
const purchaseCalls = [];
const activateCalls = [];

function normalizeLuckmailBaseUrl(value) {
  return String(value || '').trim() || 'https://mails.luckyous.com';
}
function normalizeLuckmailEmailType(value) {
  return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
    ? String(value || '').trim()
    : 'ms_graph';
}
function normalizeLuckmailPurchase(value) {
  return value;
}
function normalizeLuckmailPurchases(value) {
  return value.purchases || [];
}
async function getState() {
  return currentState;
}
function createLuckmailClient() {
  return {
    user: {
      async purchaseEmails(projectCode, quantity, options) {
        purchaseCalls.push({ projectCode, quantity, options });
        return { purchases: [] };
      },
    },
  };
}
async function findReusableLuckmailPurchaseForFlow() {
  return {
    id: 99,
    email_address: 'reuse@outlook.com',
    token: 'tok-reuse',
  };
}
async function activateLuckmailPurchaseForFlow(state, client, purchase, options) {
  activateCalls.push({ state, purchase, options });
  currentState.currentLuckmailPurchase = purchase;
  currentState.email = purchase.email_address;
  return purchase;
}

${bundle}

return {
  ensureLuckmailPurchaseForFlow,
  snapshot() {
    return { currentState, purchaseCalls, activateCalls };
  },
};
`);

  const api = factory({
    luckmailApiKey: 'sk-test',
    luckmailBaseUrl: 'https://mails.luckyous.com',
    luckmailEmailType: 'ms_imap',
    luckmailDomain: 'outlook.com',
    currentLuckmailPurchase: null,
    email: null,
  });

  const purchase = await api.ensureLuckmailPurchaseForFlow();
  const snapshot = api.snapshot();

  assert.equal(purchase.id, 99);
  assert.deepStrictEqual(snapshot.purchaseCalls, []);
  assert.equal(snapshot.activateCalls[0].options.initializeCursor, true);
  assert.match(snapshot.activateCalls[0].options.logMessage, /已复用 openai 邮箱/);
});

test('activateLuckmailPurchaseForFlow builds baseline cursor from existing mails when reusing mailbox', async () => {
  const bundle = extractFunction('activateLuckmailPurchaseForFlow');

  const factory = new Function(`
let currentPurchase = null;
let currentCursor = null;
let currentEmail = null;
const buildCalls = [];

function normalizeLuckmailPurchase(value) {
  return value;
}
async function setLuckmailPurchaseState(value) {
  currentPurchase = value;
}
async function setLuckmailMailCursorState(value) {
  currentCursor = value;
}
async function setEmailState(value) {
  currentEmail = value;
}
async function addLog() {}
function buildLuckmailBaselineCursor(mails) {
  buildCalls.push(mails);
  return { messageId: 'mail-new', receivedAt: '2026-04-14 13:32:05' };
}

${bundle}

return {
  activateLuckmailPurchaseForFlow,
  snapshot() {
    return { currentPurchase, currentCursor, currentEmail, buildCalls };
  },
};
`);

  const api = factory();
  const client = {
    user: {
      async getTokenMails() {
        return {
          mails: [
            { message_id: 'mail-old', received_at: '2026-04-14 13:31:15' },
            { message_id: 'mail-new', received_at: '2026-04-14 13:32:05' },
          ],
        };
      },
    },
  };

  await api.activateLuckmailPurchaseForFlow({}, client, {
    id: 5,
    email_address: 'reuse@outlook.com',
    token: 'tok-reuse',
  }, {
    initializeCursor: true,
    logMessage: 'reuse',
  });

  const snapshot = api.snapshot();
  assert.equal(snapshot.currentPurchase.id, 5);
  assert.deepStrictEqual(snapshot.currentCursor, {
    messageId: 'mail-new',
    receivedAt: '2026-04-14 13:32:05',
  });
  assert.equal(snapshot.currentEmail, 'reuse@outlook.com');
  assert.equal(snapshot.buildCalls.length, 1);
});

test('pollLuckmailVerificationCode snapshots existing mails before accepting new LuckMail code', async () => {
  const bundle = extractFunction('pollLuckmailVerificationCode');

  const factory = new Function(`
let currentState = {
  currentLuckmailPurchase: {
    id: 7,
    email_address: 'luck@example.com',
    token: 'tok-luck',
  },
  currentLuckmailMailCursor: null,
};
const logs = [];
const cursorWrites = [];
let tokenCodeCalls = 0;

function getCurrentLuckmailPurchase(state) {
  return state.currentLuckmailPurchase;
}
function createLuckmailClient() {
  return {
    user: {
      async getTokenMails() {
        if (tokenCodeCalls === 0) {
          return {
            mails: [
              { message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
            ],
          };
        }
        return {
          mails: [
            { message_id: 'new-mail', received_at: '2026-04-14 13:32:05', verification_code: '222222' },
            { message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
          ],
        };
      },
      async getTokenCode() {
        tokenCodeCalls += 1;
        return tokenCodeCalls === 1
          ? {
            has_new_mail: true,
            verification_code: '111111',
            mail: { message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
          }
          : {
            has_new_mail: true,
            verification_code: '222222',
            mail: { message_id: 'new-mail', received_at: '2026-04-14 13:32:05', verification_code: '222222' },
          };
      },
      async getTokenMailDetail(_token, messageId) {
        return { message_id: messageId, received_at: '2026-04-14 13:32:05', verification_code: '222222' };
      },
    },
  };
}
async function getState() {
  return currentState;
}
async function setLuckmailMailCursorState(cursor) {
  currentState = { ...currentState, currentLuckmailMailCursor: cursor };
  cursorWrites.push(cursor);
}
function normalizeLuckmailMailCursor(cursor) {
  return {
    messageId: cursor?.messageId || cursor?.message_id || '',
    receivedAt: cursor?.receivedAt || cursor?.received_at || '',
  };
}
function normalizeLuckmailTimestamp(value) {
  return Date.parse(String(value || '').replace(' ', 'T') + 'Z') || 0;
}
function buildLuckmailMailCursor(mail) {
  return { messageId: mail.message_id || '', receivedAt: mail.received_at || '' };
}
function buildLuckmailBaselineCursor(mails) {
  const latest = mails[0] || null;
  return latest ? buildLuckmailMailCursor(latest) : null;
}
function isLuckmailMailNewerThanCursor(mail, cursor) {
  if (!cursor?.messageId && !cursor?.receivedAt) return true;
  if (mail.message_id === cursor.messageId) return false;
  return normalizeLuckmailTimestamp(mail.received_at) > normalizeLuckmailTimestamp(cursor.receivedAt);
}
function pickLuckmailVerificationMail(mails, filters) {
  const excludeCodes = new Set(filters.excludeCodes || []);
  for (const mail of mails || []) {
    if (!mail.verification_code || excludeCodes.has(mail.verification_code)) continue;
    return { mail, code: mail.verification_code };
  }
  return null;
}
function normalizeLuckmailTokenCode(result) {
  return result;
}
async function resolveLuckmailVerificationMail(client, token, filters, tokenCodeResult) {
  if (tokenCodeResult?.mail) {
    const inline = pickLuckmailVerificationMail([tokenCodeResult.mail], filters);
    if (inline) return inline;
  }
  const mailList = await client.user.getTokenMails(token);
  return pickLuckmailVerificationMail(mailList.mails, filters);
}
async function addLog(message, level) {
  logs.push({ message, level });
}
function throwIfStopped() {}
function isStopError() {
  return false;
}
async function sleepWithStop() {}

${bundle}

return {
  pollLuckmailVerificationCode,
  snapshot() {
    return { currentState, cursorWrites, logs, tokenCodeCalls };
  },
};
`);

  const api = factory();
  const result = await api.pollLuckmailVerificationCode(4, await api.snapshot().currentState, {
    maxAttempts: 2,
    intervalMs: 1000,
    senderFilters: ['openai'],
    subjectFilters: ['code'],
    excludeCodes: [],
  });

  assert.equal(result.code, '222222');
  const snapshot = api.snapshot();
  assert.deepStrictEqual(snapshot.cursorWrites[0], {
    messageId: 'old-mail',
    receivedAt: '2026-04-14 13:31:15',
  });
  assert.deepStrictEqual(snapshot.cursorWrites.at(-1), {
    messageId: 'new-mail',
    receivedAt: '2026-04-14 13:32:05',
  });
  assert.equal(snapshot.logs.some((entry) => /已保存当前邮箱旧邮件快照/.test(entry.message)), true);
  assert.equal(snapshot.tokenCodeCalls, 2);
});

test('buildPersistentSettingsPayload keeps LuckMail config fields for storage.local persistence', () => {
  const bundle = [
    extractFunction('normalizePersistentSettingValue'),
    extractFunction('buildPersistentSettingsPayload'),
  ].join('\n');

  const factory = new Function(`
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
const PERSISTED_SETTING_DEFAULTS = {
  luckmailApiKey: '',
  luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
  luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
  luckmailDomain: '',
  luckmailUsedPurchases: {},
  luckmailPreserveTagId: 0,
  luckmailPreserveTagName: '保留',
};
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
function normalizeLuckmailBaseUrl(value) {
  const normalized = String(value || '').trim() || DEFAULT_LUCKMAIL_BASE_URL;
  return normalized.replace(/\\/$/, '');
}
function normalizeLuckmailEmailType(value) {
  return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
    ? String(value || '').trim()
    : DEFAULT_LUCKMAIL_EMAIL_TYPE;
}
function normalizeLuckmailPurchaseId(value) {
  const numeric = Number(value);
  return Number.isFinite(numeric) && numeric > 0 ? String(Math.floor(numeric)) : '';
}
function normalizeLuckmailUsedPurchases(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
  return Object.entries(value).reduce((result, [key, used]) => {
    const normalizedKey = normalizeLuckmailPurchaseId(key);
    if (normalizedKey) result[normalizedKey] = Boolean(used);
    return result;
  }, {});
}
function resolveLegacyAutoStepDelaySeconds() {
  return undefined;
}

${bundle}

return {
  buildPersistentSettingsPayload,
};
`);

  const api = factory();
  const payload = api.buildPersistentSettingsPayload({
    luckmailApiKey: 'sk-live-demo',
    luckmailBaseUrl: 'https://demo.example.com/',
    luckmailEmailType: 'ms_imap',
    luckmailDomain: ' outlook.com ',
  });

  assert.deepStrictEqual(payload, {
    luckmailApiKey: 'sk-live-demo',
    luckmailBaseUrl: 'https://demo.example.com',
    luckmailEmailType: 'ms_imap',
    luckmailDomain: 'outlook.com',
  });

  const statePayload = api.buildPersistentSettingsPayload({
    luckmailUsedPurchases: { 88: true, 99: false, bad: true },
    luckmailPreserveTagId: '9',
    luckmailPreserveTagName: '  保留邮箱  ',
  });
  assert.deepStrictEqual(statePayload, {
    luckmailUsedPurchases: { 88: true, 99: false },
    luckmailPreserveTagId: 9,
    luckmailPreserveTagName: '保留邮箱',
  });
});

test('listLuckmailPurchasesByProject only keeps openai purchases', async () => {
  const bundle = extractFunction('listLuckmailPurchasesByProject');

  const factory = new Function(`
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
function normalizeLuckmailProjectName(value) {
  return String(value || '').trim().toLowerCase();
}
function isLuckmailPurchaseForProject(purchase, projectCode) {
  return normalizeLuckmailProjectName(purchase.project_name || purchase.project) === normalizeLuckmailProjectName(projectCode);
}
async function getAllLuckmailPurchases() {
  return [
    { id: 1, project_name: 'OpenAi' },
    { id: 2, project_name: 'other' },
    { id: 3, project: 'openai' },
  ];
}

${bundle}

return { listLuckmailPurchasesByProject };
`);

  const api = factory();
  const result = await api.listLuckmailPurchasesByProject({}, { projectCode: 'openai' });
  assert.deepStrictEqual(result.map((item) => item.id), [1, 3]);
});

test('disableUsedLuckmailPurchases only disables locally used and non-preserved openai mailboxes', async () => {
  const bundle = extractFunction('disableUsedLuckmailPurchases');

  const factory = new Function(`
let clearedOptions = null;
const disabledCalls = [];
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';

function normalizeLuckmailPurchaseId(value) {
  const numeric = Number(value);
  return Number.isFinite(numeric) && numeric > 0 ? String(Math.floor(numeric)) : '';
}
async function ensureManualInteractionAllowed() {
  return {
    luckmailUsedPurchases: { 1: true, 2: true, 3: true },
    luckmailPreserveTagId: 9,
    luckmailPreserveTagName: '保留',
    mailProvider: 'luckmail-api',
  };
}
function getLuckmailUsedPurchases(state) {
  return state.luckmailUsedPurchases;
}
function getLuckmailPreserveTagInfo(state) {
  return {
    id: state.luckmailPreserveTagId,
    name: state.luckmailPreserveTagName,
  };
}
function isLuckmailPurchasePreserved(purchase, options) {
  return purchase.tag_id === options.preserveTagId || purchase.tag_name === options.preserveTagName;
}
function createLuckmailClient() {
  return {
    user: {
      async batchSetPurchaseDisabled(ids, disabled) {
        disabledCalls.push({ ids, disabled });
      },
    },
  };
}
async function listLuckmailPurchasesByProject() {
  return [
    { id: 1, email_address: 'used-1@outlook.com', user_disabled: 0, tag_id: 0, tag_name: '' },
    { id: 2, email_address: 'preserved@outlook.com', user_disabled: 0, tag_id: 9, tag_name: '保留' },
    { id: 3, email_address: 'already-disabled@outlook.com', user_disabled: 1, tag_id: 0, tag_name: '' },
    { id: 4, email_address: 'unused@outlook.com', user_disabled: 0, tag_id: 0, tag_name: '' },
  ];
}
async function getState() {
  return {
    currentLuckmailPurchase: { id: 1 },
    mailProvider: 'luckmail-api',
  };
}
function getCurrentLuckmailPurchase(state) {
  return state.currentLuckmailPurchase;
}
function isLuckmailProvider(state) {
  return state.mailProvider === 'luckmail-api';
}
async function clearLuckmailRuntimeState(options) {
  clearedOptions = options;
}
async function addLog() {}

${bundle}

return {
  disableUsedLuckmailPurchases,
  snapshot() {
    return { disabledCalls, clearedOptions };
  },
};
`);

  const api = factory();
  const result = await api.disableUsedLuckmailPurchases();
  const snapshot = api.snapshot();

  assert.deepStrictEqual(result.disabledIds, [1]);
  assert.deepStrictEqual(snapshot.disabledCalls, [{ ids: [1], disabled: 1 }]);
  assert.deepStrictEqual(snapshot.clearedOptions, { clearEmail: true });
});

test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => {
  const bundle = [
    extractFunction('buildContributionModeState'),
    extractFunction('resetState'),
  ].join('\n');

  const factory = new Function([
    'let cleared = false;',
    'let storedPayload = null;',
    "const LOG_PREFIX = '[test]';",
    "const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = '保留';",
    'const DEFAULT_STATE = {',
    "  luckmailApiKey: '',",
    "  luckmailBaseUrl: 'https://mails.luckyous.com',",
    "  luckmailEmailType: 'ms_graph',",
    "  luckmailDomain: '',",
    "  panelMode: 'cpa',",
    '  luckmailUsedPurchases: {},',
    '  luckmailPreserveTagId: 0,',
    "  luckmailPreserveTagName: '保留',",
    "  currentLuckmailPurchase: { token: 'stale' },",
    "  currentLuckmailMailCursor: { messageId: 'stale' },",
    '  currentPhoneActivation: null,',
    '  reusablePhoneActivation: null,',
    '  email: null,',
    '};',
    'const CONTRIBUTION_RUNTIME_DEFAULTS = {',
    '  contributionMode: false,',
    "  contributionSessionId: '',",
    "  contributionAuthUrl: '',",
    "  contributionAuthState: '',",
    "  contributionCallbackUrl: '',",
    "  contributionStatus: '',",
    "  contributionStatusMessage: '',",
    '  contributionLastPollAt: 0,',
    "  contributionCallbackStatus: 'idle',",
    "  contributionCallbackMessage: '',",
    '  contributionAuthOpenedAt: 0,',
    '  contributionAuthTabId: 0,',
    '};',
    'const CONTRIBUTION_RUNTIME_KEYS = Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS);',
    'function normalizeLuckmailBaseUrl(value) {',
    "  const normalized = String(value || '').trim() || 'https://mails.luckyous.com';",
    "  return normalized.replace(/\\/$/, '');",
    '}',
    'function normalizeLuckmailEmailType(value) {',
    "  return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())",
    "    ? String(value || '').trim()",
    "    : 'ms_graph';",
    '}',
    'function normalizeLuckmailUsedPurchases(value) {',
    '  return value || {};',
    '}',
    'async function getPersistedSettings() {',
    "  return { mailProvider: '163' };",
    '}',
    'async function getPersistedAliasState() {',
    '  return {};',
    '}',
    'const chrome = {',
    '  storage: {',
    '    session: {',
    '      async get() {',
    '        return {',
    "          seenCodes: ['seen-1'],",
    "          seenInbucketMailIds: ['mail-1'],",
    "          accounts: [{ email: 'saved@example.com' }],",
    "          tabRegistry: { foo: { tabId: 1 } },",
    "          sourceLastUrls: { foo: 'https://example.com' },",
    "          reusablePhoneActivation: { activationId: 'rx-001', phoneNumber: '66951112222', provider: 'hero-sms', serviceCode: 'dr', countryId: 52, successfulUses: 1, maxUses: 3 },",
    "          luckmailApiKey: 'sk-session',",
    "          luckmailBaseUrl: 'https://demo.example.com/',",
    "          luckmailEmailType: 'ms_imap',",
    "          luckmailDomain: 'outlook.com',",
    "          luckmailUsedPurchases: { 88: true },",
    '          luckmailPreserveTagId: 9,',
    "          luckmailPreserveTagName: '保留',",
    '        };',
    '      },',
    '      async clear() {',
    '        cleared = true;',
    '      },',
    '      async set(payload) {',
    '        storedPayload = payload;',
    '      },',
    '    },',
    '  },',
    '};',
    bundle,
    'return {',
    '  resetState,',
    '  snapshot() {',
    '    return { cleared, storedPayload };',
    '  },',
    '};',
  ].join('\n'));

  const api = factory();
  await api.resetState();
  const snapshot = api.snapshot();

  assert.equal(snapshot.cleared, true);
  assert.equal(snapshot.storedPayload.luckmailApiKey, 'sk-session');
  assert.equal(snapshot.storedPayload.luckmailBaseUrl, 'https://demo.example.com');
  assert.equal(snapshot.storedPayload.luckmailEmailType, 'ms_imap');
  assert.equal(snapshot.storedPayload.luckmailDomain, 'outlook.com');
  assert.deepStrictEqual(snapshot.storedPayload.luckmailUsedPurchases, { 88: true });
  assert.equal(snapshot.storedPayload.luckmailPreserveTagId, 9);
  assert.equal(snapshot.storedPayload.luckmailPreserveTagName, '保留');
  assert.equal(snapshot.storedPayload.currentLuckmailPurchase, null);
  assert.equal(snapshot.storedPayload.currentLuckmailMailCursor, null);
  assert.deepStrictEqual(snapshot.storedPayload.reusablePhoneActivation, {
    activationId: 'rx-001',
    phoneNumber: '66951112222',
    provider: 'hero-sms',
    serviceCode: 'dr',
    countryId: 52,
    successfulUses: 1,
    maxUses: 3,
  });
  assert.equal(snapshot.storedPayload.currentPhoneActivation, null);
});

test('message router platform verify marks current LuckMail purchase as used and clears runtime state', async () => {
  const { router, snapshot } = createLuckmailPlatformVerifyRouter({
    state: {
    mailProvider: 'luckmail-api',
    currentHotmailAccountId: null,
    currentLuckmailPurchase: {
      id: 123,
      email_address: 'demo@outlook.com',
    },
    email: 'demo@outlook.com',
    },
  });

  await router.handleStepData(10, {
    localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
  });

  const result = snapshot();
  assert.deepStrictEqual(result.usedMarker, { purchaseId: 123, used: true });
  assert.deepStrictEqual(result.clearedOptions, { clearEmail: true });
  assert.equal(result.logs.at(-1).message, '当前 LuckMail 邮箱运行态已清空，下轮将优先复用未用邮箱或重新购买邮箱。');
});

test('message router marks current LuckMail purchase as used on Plus platform verify step 13', async () => {
  const { router, snapshot } = createLuckmailPlatformVerifyRouter({
    state: {
    plusModeEnabled: true,
    mailProvider: 'luckmail-api',
    currentHotmailAccountId: null,
    currentLuckmailPurchase: {
      id: 456,
      email_address: 'plus@outlook.com',
    },
    email: 'plus@outlook.com',
    },
    stepKeyByStep: {
      10: 'oauth-login',
      13: 'platform-verify',
    },
  });

  await router.handleStepData(10, {});
  assert.equal(snapshot().usedMarker, null);

  await router.handleStepData(13, {
    localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
  });

  const result = snapshot();
  assert.deepStrictEqual(result.usedMarker, { purchaseId: 456, used: true });
  assert.equal(result.logs.some((entry) => /已在本地标记为已用/.test(entry.message)), true);
});

test('setLuckmailPurchaseUsedState persists used map to storage.local so reload keeps it non-reusable', async () => {
  const bundle = [
    extractFunction('getLuckmailUsedPurchases'),
    extractFunction('setLuckmailUsedPurchasesState'),
    extractFunction('setLuckmailPurchaseUsedState'),
  ].join('\n');

  const factory = new Function(`
let sessionState = {
  luckmailUsedPurchases: { 7: true },
};
let persistentUpdates = [];
let sessionUpdates = [];
let broadcasts = [];
function normalizeLuckmailPurchaseId(value) {
  const numeric = Number(value);
  return Number.isFinite(numeric) && numeric > 0 ? String(Math.floor(numeric)) : '';
}
function normalizeLuckmailUsedPurchases(value) {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
  return Object.entries(value).reduce((result, [key, used]) => {
    const normalizedKey = normalizeLuckmailPurchaseId(key);
    if (normalizedKey) result[normalizedKey] = Boolean(used);
    return result;
  }, {});
}
async function getState() {
  return { ...sessionState };
}
async function setPersistentSettings(updates) {
  persistentUpdates.push(updates);
}
async function setState(updates) {
  sessionUpdates.push(updates);
  sessionState = { ...sessionState, ...updates };
}
function broadcastDataUpdate(updates) {
  broadcasts.push(updates);
}

${bundle}

return {
  setLuckmailPurchaseUsedState,
  snapshot() {
    return { sessionState, persistentUpdates, sessionUpdates, broadcasts };
  },
};
`);

  const api = factory();
  const result = await api.setLuckmailPurchaseUsedState(88, true);
  const snapshot = api.snapshot();

  assert.deepStrictEqual(result, { purchaseId: 88, used: true });
  assert.deepStrictEqual(snapshot.sessionState.luckmailUsedPurchases, { 7: true, 88: true });
  assert.deepStrictEqual(snapshot.persistentUpdates, [
    { luckmailUsedPurchases: { 7: true, 88: true } },
  ]);
  assert.deepStrictEqual(snapshot.sessionUpdates, [
    { luckmailUsedPurchases: { 7: true, 88: true } },
  ]);
  assert.deepStrictEqual(snapshot.broadcasts, [
    { luckmailUsedPurchases: { 7: true, 88: true } },
  ]);
});

test('setLuckmailPreserveTagInfo persists tag cache to storage.local', async () => {
  const bundle = extractFunction('setLuckmailPreserveTagInfo');

  const factory = new Function(`
let persistentUpdates = [];
let sessionUpdates = [];
let broadcasts = [];
const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = '保留';
function normalizeLuckmailTags(tags) {
  return (Array.isArray(tags) ? tags : []).map((tag) => ({
    id: Number(tag?.id) || 0,
    name: String(tag?.name || '').trim(),
  })).filter((tag) => tag.id > 0 || tag.name);
}
async function setPersistentSettings(updates) {
  persistentUpdates.push(updates);
}
async function setState(updates) {
  sessionUpdates.push(updates);
}
function broadcastDataUpdate(updates) {
  broadcasts.push(updates);
}

${bundle}

return {
  setLuckmailPreserveTagInfo,
  snapshot() {
    return { persistentUpdates, sessionUpdates, broadcasts };
  },
};
`);

  const api = factory();
  const result = await api.setLuckmailPreserveTagInfo({ id: '12', name: ' 保留邮箱 ' });
  const expected = {
    luckmailPreserveTagId: 12,
    luckmailPreserveTagName: '保留邮箱',
  };

  assert.deepStrictEqual(result, expected);
  assert.deepStrictEqual(api.snapshot().persistentUpdates, [expected]);
  assert.deepStrictEqual(api.snapshot().sessionUpdates, [expected]);
  assert.deepStrictEqual(api.snapshot().broadcasts, [expected]);
});
