import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";

const REAL_DATE = Date;

function createFixedDate(isoString) {
  const fixedTime = new REAL_DATE(isoString).getTime();

  return class FixedDate extends REAL_DATE {
    constructor(value) {
      super(value === undefined ? fixedTime : value);
    }

    static now() {
      return fixedTime;
    }

    static parse(value) {
      return REAL_DATE.parse(value);
    }

    static UTC(...args) {
      return REAL_DATE.UTC(...args);
    }
  };
}

function flushMicrotasks() {
  return new Promise((resolve) => setImmediate(resolve));
}

function createStorageApi(initialState = {}) {
  const state = { ...initialState };

  return {
    state,
    api: {
      get(defaults, callback) {
        const result = { ...defaults };
        Object.keys(defaults).forEach((key) => {
          if (Object.prototype.hasOwnProperty.call(state, key)) {
            result[key] = state[key];
          }
        });
        callback(result);
      },
      set(values, callback) {
        Object.assign(state, values);
        callback?.();
      },
      remove(keys, callback) {
        const list = Array.isArray(keys) ? keys : [keys];
        list.forEach((key) => {
          delete state[key];
        });
        callback?.();
      }
    }
  };
}

function createActiveStoredLicense(overrides = {}) {
  return {
    code: "ZS-Y-AAAA-BBBB-CCCC",
    planType: "weekly",
    status: "active",
    activatedAt: "2026-04-01T00:00:00.000Z",
    expiresAt: "2026-04-30T00:00:00.000Z",
    lastValidatedAt: "2026-04-18T07:00:00.000Z",
    nextCheckAt: "2026-04-19T00:00:00.000Z",
    deviceId: "dev_1111111111111111",
    ...overrides
  };
}

function loadBackgroundScript(options = {}) {
  const {
    storageState = {},
    fetchImpl = async () => ({
      ok: true,
      json: async () => ({ ok: true })
    }),
    fixedNow = "2026-04-18T08:00:00.000Z"
  } = options;
  const storage = createStorageApi(storageState);
  let messageListener = null;
  const timers = [];
  let nextTimerId = 1;
  const sourcePath = new URL("../bg.js", import.meta.url);
  const source = `${fs.readFileSync(sourcePath, "utf8")}
globalThis.__bgTestExports = {
  getStorage,
  normalizeLead,
  mergeLead,
  getLicenseState,
  requestLicenseApi
};
`;

  const chrome = {
    runtime: {
      id: "test-runtime-id",
      getManifest: () => ({ version: "7.0.0" }),
      getPlatformInfo: (callback) => callback({ os: "mac", arch: "arm64", nacl_arch: "arm64" }),
      onMessage: {
        addListener(listener) {
          messageListener = listener;
        }
      }
    },
    action: {
      onClicked: {
        addListener() {}
      }
    },
    tabs: {
      create(_data, callback) {
        callback({ id: 1, windowId: 1 });
      },
      query(_query, callback) {
        callback([]);
      },
      update(id, _data, callback) {
        callback({ id, windowId: 1 });
      }
    },
    windows: {
      update(id, _data, callback) {
        callback({ id });
      },
      create(_data, callback) {
        callback({ id: 1 });
      }
    },
    storage: {
      local: storage.api
    }
  };

  const context = {
    console,
    chrome,
    fetch: fetchImpl,
    navigator: {
      userAgent: "UnitTest/1.0",
      language: "zh-CN",
      languages: ["zh-CN", "en-US"],
      platform: "MacIntel",
      hardwareConcurrency: 8,
      deviceMemory: 8,
      maxTouchPoints: 0
    },
    crypto: {
      randomUUID: () => "11111111-1111-1111-1111-111111111111",
      getRandomValues(array) {
        array.fill(1);
        return array;
      },
      subtle: {
        async digest() {
          return new Uint8Array(32).fill(2).buffer;
        }
      }
    },
    Date: createFixedDate(fixedNow),
    Intl,
    URL,
    AbortController,
    TextEncoder,
    Uint8Array,
    Map,
    Set,
    Promise,
    JSON,
    Math,
    Error,
    setTimeout(fn, delay) {
      const timer = { id: nextTimerId += 1, fn, delay, cleared: false };
      timers.push(timer);
      return timer.id;
    },
    clearTimeout(id) {
      const timer = timers.find((item) => item.id === id);
      if (timer) {
        timer.cleared = true;
      }
    }
  };

  vm.createContext(context);
  vm.runInContext(source, context, { filename: sourcePath.pathname });

  return {
    exports: context.__bgTestExports,
    storageState: storage.state,
    timers,
    async sendRuntimeMessage(message, sender = {}) {
      if (!messageListener) {
        throw new Error("Background message listener was not registered.");
      }

      return new Promise((resolve, reject) => {
        try {
          const maybeAsync = messageListener(message, sender, (response) => resolve(response));
          if (maybeAsync !== true) {
            resolve(undefined);
          }
        } catch (error) {
          reject(error);
        }
      });
    }
  };
}

function loadInjectedScript(options = {}) {
  const { fetchImpl } = options;
  const sourcePath = new URL("../injected.js", import.meta.url);
  const source = fs.readFileSync(sourcePath, "utf8");
  const postedMessages = [];

  function FakeXMLHttpRequest() {}
  FakeXMLHttpRequest.prototype.open = function () {};
  FakeXMLHttpRequest.prototype.send = function () {};
  FakeXMLHttpRequest.prototype.addEventListener = function () {};

  const windowObject = {
    fetch: fetchImpl,
    postMessage(message) {
      postedMessages.push(message);
    }
  };

  const context = {
    console,
    window: windowObject,
    location: { origin: "https://www.google.com" },
    XMLHttpRequest: FakeXMLHttpRequest,
    Promise,
    setTimeout,
    clearTimeout
  };

  vm.createContext(context);
  vm.runInContext(source, context, { filename: sourcePath.pathname });

  return {
    window: windowObject,
    postedMessages
  };
}

function createContentScriptDocument() {
  const listeners = new Map();
  const appendedScripts = [];

  return {
    readyState: "loading",
    listeners,
    appendedScripts,
    getElementById() {
      return null;
    },
    createElement() {
      return {
        id: "",
        src: "",
        onload: null,
        remove() {}
      };
    },
    addEventListener(type, handler) {
      listeners.set(type, handler);
    },
    querySelector() {
      return null;
    },
    querySelectorAll() {
      return [];
    },
    head: {
      appendChild(node) {
        appendedScripts.push(node);
      }
    },
    documentElement: {
      appendChild(node) {
        appendedScripts.push(node);
      }
    }
  };
}

function loadContentScript(options = {}) {
  const {
    claimJob,
    waitForImpl = async () => false,
    messageHandler = null,
    confirmImpl = () => true
  } = options;
  const timers = [];
  let nextTimerId = 1;
  const document = createContentScriptDocument();
  const sourcePath = new URL("../contentScript.js", import.meta.url);
  const source = fs.readFileSync(sourcePath, "utf8").replace(
    /\n\}\)\(\);\s*$/,
    `
  globalThis.__contentScriptTestHooks = {
    STATE,
    maybeAutostart,
    bufferLeadsForSave,
    flushLeadBuffer,
    clearLeads,
    processQueuedCandidatesPhase,
    setWaitFor(fn) {
      waitFor = fn;
    },
    setRunCollection(fn) {
      runCollection = fn;
    },
    setProcessCandidate(fn) {
      processCandidate = fn;
    },
    setGetNextQueuedCandidate(fn) {
      getNextQueuedCandidate = fn;
    },
    setReturnToResults(fn) {
      returnToResults = fn;
    },
    setEnsureResultsContext(fn) {
      ensureResultsContext = fn;
    },
    setQueueVisibleCandidates(fn) {
      queueVisibleCandidates = fn;
    },
    setWindowConfirm(fn) {
      window.confirm = fn;
    }
  };
})();
`
  );

  const chrome = {
    runtime: {
      getURL(pathname) {
        return `chrome-extension://test/${pathname}`;
      },
      sendMessage(message, callback) {
        if (messageHandler) {
          callback(messageHandler(message));
          return;
        }

        if (message.action === "CLAIM_COLLECTION_JOB") {
          callback({ ok: true, job: claimJob });
          return;
        }

        callback({ ok: true });
      }
    }
  };

  const context = {
    console,
    chrome,
    document,
    window: {
      addEventListener() {},
      removeEventListener() {},
      confirm: confirmImpl
    },
    location: { href: "https://www.google.com/maps/search/test" },
    navigator: { language: "zh-CN" },
    MutationObserver: class {
      observe() {}
      disconnect() {}
    },
    URL,
    Map,
    Set,
    Promise,
    JSON,
    Math,
    Error,
    setTimeout(fn, delay) {
      const timer = { id: nextTimerId += 1, fn, delay, cleared: false };
      timers.push(timer);
      return timer.id;
    },
    clearTimeout(id) {
      const timer = timers.find((item) => item.id === id);
      if (timer) {
        timer.cleared = true;
      }
    }
  };

  vm.createContext(context);
  vm.runInContext(source, context, { filename: sourcePath.pathname });
  context.__contentScriptTestHooks.setWaitFor(waitForImpl);

  return {
    hooks: context.__contentScriptTestHooks,
    timers
  };
}

class FakeElement {
  constructor(id) {
    this.id = id;
    this.textContent = "";
    this.dataset = {};
    this.hidden = false;
    this.value = "";
    this.listeners = new Map();
  }

  addEventListener(type, handler) {
    this.listeners.set(type, handler);
  }

  async click() {
    const handler = this.listeners.get("click");
    if (handler) {
      await handler({ target: this });
    }
  }
}

class FakeDomNode {
  constructor(tagName = "div", id = "") {
    this.tagName = tagName.toUpperCase();
    this.id = id;
    this.textContent = "";
    this.innerHTML = "";
    this.dataset = {};
    this.hidden = false;
    this.value = "";
    this.disabled = false;
    this.className = "";
    this.children = [];
    this.listeners = new Map();
    this.classList = {
      add: (...names) => {
        const current = new Set(String(this.className || "").split(/\s+/).filter(Boolean));
        names.forEach((name) => current.add(name));
        this.className = [...current].join(" ");
      },
      toggle: (name) => {
        const current = new Set(String(this.className || "").split(/\s+/).filter(Boolean));
        if (current.has(name)) {
          current.delete(name);
          this.className = [...current].join(" ");
          return false;
        }
        current.add(name);
        this.className = [...current].join(" ");
        return true;
      }
    };
  }

  appendChild(node) {
    this.children.push(node);
    return node;
  }

  addEventListener(type, handler) {
    this.listeners.set(type, handler);
  }

  async click() {
    const handler = this.listeners.get("click");
    if (handler) {
      await handler({ target: this });
    }
  }
}

function createLicenseDocument() {
  const elementIds = [
    "licenseStatusChip",
    "licenseMessage",
    "planTypeText",
    "activatedAtText",
    "expiresAtText",
    "deviceIdText",
    "licenseReminder",
    "pageStatus",
    "activateBtn",
    "refreshBtn",
    "copyDeviceBtn",
    "licenseCodeInput"
  ];
  const elements = new Map(elementIds.map((id) => [id, new FakeElement(id)]));

  return {
    elements,
    getElementById(id) {
      return elements.get(id) || null;
    }
  };
}

function loadLicenseScript(options = {}) {
  const { responses = [] } = options;
  const document = createLicenseDocument();
  const sourcePath = new URL("../js/license.js", import.meta.url);
  const source = `${fs.readFileSync(sourcePath, "utf8")}
globalThis.__licenseTestExports = { loadLicense };
`;
  let responseIndex = 0;

  const chrome = {
    runtime: {
      sendMessage(_message, callback) {
        const next = responses[Math.min(responseIndex, responses.length - 1)];
        responseIndex += 1;
        callback(next);
      }
    }
  };

  const context = {
    console,
    chrome,
    document,
    navigator: {
      clipboard: {
        async writeText() {}
      }
    },
    Intl,
    Date: createFixedDate("2026-04-18T08:00:00.000Z")
  };

  vm.createContext(context);
  vm.runInContext(source, context, { filename: sourcePath.pathname });

  return {
    document
  };
}

function createDashboardDocument() {
  const elementIds = [
    "refreshBtn",
    "licenseBtn",
    "licenseBannerBtn",
    "downloadCsvBtn",
    "downloadXlsxBtn",
    "clearBtn",
    "licenseBanner",
    "licenseBannerTitle",
    "licenseBannerText",
    "summaryText",
    "resultsContainer",
    "emptyTableWrap",
    "resultsTable"
  ];
  const elements = new Map(elementIds.map((id) => [id, new FakeDomNode("div", id)]));

  return {
    elements,
    getElementById(id) {
      return elements.get(id) || null;
    },
    createElement(tagName) {
      return new FakeDomNode(tagName);
    }
  };
}

function loadDashboardScript(options = {}) {
  const { responses = [] } = options;
  const document = createDashboardDocument();
  const sourcePath = new URL("../js/dashboard.js", import.meta.url);
  const source = `${fs.readFileSync(sourcePath, "utf8")}
globalThis.__dashboardTestExports = {
  downloadCsv,
  setState(next) {
    if (Object.prototype.hasOwnProperty.call(next, "license")) {
      currentLicense = next.license;
    }
    if (Object.prototype.hasOwnProperty.call(next, "leads")) {
      currentLeads = next.leads;
    }
  }
};
`;
  let responseIndex = 0;
  const createdUrls = [];
  const anchors = [];

  const chrome = {
    runtime: {
      sendMessage(_message, callback) {
        const next = responses[Math.min(responseIndex, responses.length - 1)] || { ok: true };
        responseIndex += 1;
        callback(next);
      }
    }
  };

  const context = {
    console,
    chrome,
    document,
    Intl,
    Date: createFixedDate("2026-04-18T08:00:00.000Z"),
    Blob,
    URL: {
      createObjectURL(blob) {
        const url = `blob:test-${createdUrls.length + 1}`;
        createdUrls.push({ url, blob });
        return url;
      },
      revokeObjectURL() {}
    },
    XLSX: {
      utils: {
        book_new() {
          return {};
        },
        aoa_to_sheet() {
          return {};
        },
        sheet_add_json() {},
        book_append_sheet() {}
      },
      writeFile() {}
    },
    window: {
      alert() {},
      confirm() {
        return true;
      }
    }
  };

  const originalCreateElement = document.createElement.bind(document);
  document.createElement = (tagName) => {
    const node = originalCreateElement(tagName);
    if (String(tagName).toLowerCase() === "a") {
      node.click = async () => {
        anchors.push({ href: node.href, download: node.download });
      };
    }
    return node;
  };

  vm.createContext(context);
  vm.runInContext(source, context, { filename: sourcePath.pathname });

  return {
    exports: context.__dashboardTestExports,
    createdUrls,
    anchors,
    document
  };
}

test("getStorage preserves existing updatedAt values for stored leads", async () => {
  const storedLead = {
    name: "Alpha",
    updatedAt: "2025-01-02T03:04:05.000Z"
  };
  const { exports, storageState } = loadBackgroundScript({
    storageState: {
      gmaps_leads_v3: [storedLead]
    }
  });

  const leads = await exports.getStorage();

  assert.equal(leads[0].updatedAt, storedLead.updatedAt);
  assert.equal(storageState.gmaps_leads_v3[0].updatedAt, storedLead.updatedAt);
});

test("mergeLead only refreshes updatedAt when the lead actually changes", () => {
  const fixedNow = "2026-04-18T08:00:00.000Z";
  const { exports } = loadBackgroundScript({ fixedNow });
  const existing = {
    name: "Alpha",
    phone: "123",
    updatedAt: "2025-01-02T03:04:05.000Z"
  };

  const unchanged = exports.mergeLead(existing, { name: "Alpha", phone: "123" });
  const changed = exports.mergeLead(existing, { name: "Alpha", phone: "123456" });

  assert.equal(unchanged.updatedAt, existing.updatedAt);
  assert.equal(changed.updatedAt, fixedNow);
});

test("getLicenseState uses cached data until nextCheckAt unless forceValidate is requested", async () => {
  let fetchCalls = 0;
  const storedLicense = createActiveStoredLicense();
  const { exports } = loadBackgroundScript({
    storageState: {
      gmaps_license_v1: storedLicense,
      gmaps_device_id_v1: storedLicense.deviceId
    },
    fetchImpl: async () => {
      fetchCalls += 1;
      return {
        ok: true,
        json: async () => ({
          ok: true,
          code: storedLicense.code,
          planType: storedLicense.planType,
          status: "active",
          activatedAt: storedLicense.activatedAt,
          expiresAt: storedLicense.expiresAt
        })
      };
    }
  });

  const cachedLicense = await exports.getLicenseState();
  const refreshedLicense = await exports.getLicenseState({ forceValidate: true });

  assert.equal(fetchCalls, 1);
  assert.equal(cachedLicense.status, "active");
  assert.equal(refreshedLicense.status, "active");
});

test("CLAIM_COLLECTION_JOB ignores unrelated maps tabs and keeps the pending job", async () => {
  const claimJob = {
    collectionId: "job_123",
    query: "door supplier",
    createdAt: Date.parse("2026-04-18T07:55:00.000Z"),
    enrichEmail: true
  };
  const { storageState, sendRuntimeMessage } = loadBackgroundScript({
    storageState: {
      gmaps_pending_job_v1: claimJob
    }
  });

  const response = await sendRuntimeMessage(
    { action: "CLAIM_COLLECTION_JOB" },
    { tab: { url: "https://www.google.com/maps/search/florist" } }
  );

  assert.equal(response.ok, true);
  assert.equal(response.job, null);
  assert.equal(storageState.gmaps_pending_job_v1?.collectionId, claimJob.collectionId);
});

test("CLAIM_COLLECTION_JOB preserves the pending job until collection actually starts", async () => {
  const claimJob = {
    collectionId: "job_123",
    query: "door supplier",
    createdAt: Date.parse("2026-04-18T07:55:00.000Z"),
    enrichEmail: true
  };
  const activeLicense = createActiveStoredLicense();
  const { storageState, sendRuntimeMessage } = loadBackgroundScript({
    storageState: {
      gmaps_pending_job_v1: claimJob,
      gmaps_active_collection_v1: {
        collectionId: claimJob.collectionId,
        query: claimJob.query,
        enrichEmail: true,
        state: {
          running: false,
          runStats: {},
          diagnostics: {},
          status: {}
        }
      },
      gmaps_license_v1: activeLicense,
      gmaps_device_id_v1: activeLicense.deviceId
    }
  });

  const firstClaim = await sendRuntimeMessage(
    { action: "CLAIM_COLLECTION_JOB" },
    { tab: { url: "https://www.google.com/maps/search/door%20supplier" } }
  );
  const secondClaim = await sendRuntimeMessage(
    { action: "CLAIM_COLLECTION_JOB" },
    { tab: { url: "https://www.google.com/maps/search/door%20supplier" } }
  );

  assert.equal(firstClaim.job?.collectionId, claimJob.collectionId);
  assert.equal(secondClaim.job?.collectionId, claimJob.collectionId);
  assert.equal(storageState.gmaps_pending_job_v1?.collectionId, claimJob.collectionId);

  await sendRuntimeMessage({
    action: "UPDATE_ACTIVE_COLLECTION_STATE",
    collectionId: claimJob.collectionId,
    state: {
      running: true,
      runStats: {},
      diagnostics: {},
      status: {}
    }
  });

  assert.equal(storageState.gmaps_pending_job_v1, undefined);
});

test("fetch hook returns the response before cloned body capture finishes", async () => {
  let resolveOriginalFetch;
  let resolveBodyText;
  const response = {
    url: "https://www.google.com/maps/preview/entity",
    clone() {
      return {
        text: () => new Promise((resolve) => {
          resolveBodyText = resolve;
        })
      };
    }
  };
  const { window, postedMessages } = loadInjectedScript({
    fetchImpl: () => new Promise((resolve) => {
      resolveOriginalFetch = () => resolve(response);
    })
  });

  let resolved = false;
  const fetchPromise = window.fetch("https://www.google.com/maps/preview/entity");
  fetchPromise.then(() => {
    resolved = true;
  });

  resolveOriginalFetch();
  await flushMicrotasks();

  assert.equal(resolved, true);

  resolveBodyText('/*""*/{"d":"payload"}');
  await fetchPromise;
  await flushMicrotasks();

  assert.equal(postedMessages.length, 1);
});

test("fetch hook captures same-origin relative Maps requests using the effective response URL", async () => {
  const response = {
    url: "https://www.google.com/maps/preview/entity",
    clone() {
      return {
        async text() {
          return '/*""*/{"d":"payload"}';
        }
      };
    }
  };
  const { window, postedMessages } = loadInjectedScript({
    fetchImpl: async () => response
  });

  await window.fetch("/maps/preview/entity");
  await flushMicrotasks();

  assert.equal(postedMessages.length, 1);
  assert.equal(postedMessages[0].url, response.url);
});

test("autostart keeps the claimed job for retry when results are not ready yet", async () => {
  const claimJob = {
    collectionId: "job_123",
    query: "door supplier",
    enrichEmail: true
  };
  const { hooks, timers } = loadContentScript({
    claimJob,
    waitForImpl: async () => false
  });
  let runCollectionCalls = 0;
  hooks.setRunCollection(async () => {
    runCollectionCalls += 1;
  });

  await hooks.maybeAutostart();

  assert.equal(hooks.STATE.autostartRequested, false);
  assert.equal(hooks.STATE.pendingAutostartJob?.query, claimJob.query);
  assert.equal(timers.some((timer) => timer.delay > 0 && !timer.cleared), true);
  assert.equal(runCollectionCalls, 0);
});

test("license refresh click keeps the error status when refresh fails", async () => {
  const activeLicense = {
    status: "active",
    isActive: true,
    message: "授权有效。",
    planLabel: "体验周卡",
    activatedAt: "2026-04-01T00:00:00.000Z",
    expiresAt: "2026-04-30T00:00:00.000Z",
    deviceId: "dev_1111111111111111"
  };
  const { document } = loadLicenseScript({
    responses: [
      { ok: true, license: activeLicense },
      { ok: false, error: "授权服务暂时不可用。", license: activeLicense }
    ]
  });

  await flushMicrotasks();
  await document.getElementById("refreshBtn").click();

  assert.equal(document.getElementById("pageStatus").textContent, "授权服务暂时不可用。");
});

test("flushLeadBuffer keeps buffered leads when persistence fails", async () => {
  const { hooks } = loadContentScript({
    messageHandler(message) {
      if (message.action === "MERGE_LEADS") {
        return { ok: false, error: "保存失败" };
      }

      return { ok: true };
    }
  });
  hooks.STATE.collectionId = "job_123";

  const lead = {
    name: "Alpha Doors",
    address: "1 Main St",
    phone: "123456",
    website: "https://alpha.example"
  };

  const result = await hooks.bufferLeadsForSave([lead], {
    forceFlush: true,
    reason: "test-failure"
  });

  assert.equal(result.ok, false);
  assert.equal(hooks.STATE.pendingLeadBuffer.length, 1);
  assert.equal(hooks.STATE.pendingLeadBuffer[0].name, lead.name);
});

test("CLEAR_LEADS removes the pending autostart job as part of a full reset", async () => {
  const claimJob = {
    collectionId: "job_123",
    query: "door supplier",
    createdAt: Date.parse("2026-04-18T07:55:00.000Z"),
    enrichEmail: true
  };
  const { storageState, sendRuntimeMessage } = loadBackgroundScript({
    storageState: {
      gmaps_pending_job_v1: claimJob,
      gmaps_leads_v3: [{ name: "Alpha" }]
    }
  });

  const response = await sendRuntimeMessage({ action: "CLEAR_LEADS" });

  assert.equal(response.ok, true);
  assert.equal(storageState.gmaps_pending_job_v1, undefined);
});

test("clearLeads stops the running collection state before wiping local data", async () => {
  const { hooks } = loadContentScript({
    messageHandler(message) {
      if (message.action === "CLEAR_LEADS") {
        return { ok: true };
      }

      return { ok: true };
    }
  });
  hooks.STATE.running = true;
  hooks.STATE.resumeRequested = true;
  hooks.STATE.pendingAutostartJob = { query: "door supplier" };

  await hooks.clearLeads();

  assert.equal(hooks.STATE.running, false);
  assert.equal(hooks.STATE.resumeRequested, false);
  assert.equal(hooks.STATE.pendingAutostartJob, null);
});

test("processQueuedCandidatesPhase continues when results recovery succeeds", async () => {
  const { hooks, timers } = loadContentScript();
  let nextCallCount = 0;

  hooks.STATE.running = true;
  hooks.setGetNextQueuedCandidate(() => {
    nextCallCount += 1;
    if (nextCallCount === 1) {
      return { key: "candidate-1", name: "Alpha Doors" };
    }

    return null;
  });
  hooks.setProcessCandidate(async () => true);
  hooks.setReturnToResults(async () => false);
  hooks.setEnsureResultsContext(async () => true);
  hooks.setQueueVisibleCandidates(() => ({ newlySeenCount: 0 }));

  const resultPromise = hooks.processQueuedCandidatesPhase();
  await flushMicrotasks();
  while (timers.some((timer) => !timer.cleared)) {
    const nextTimer = timers.find((timer) => !timer.cleared);
    nextTimer.cleared = true;
    nextTimer.fn();
    await flushMicrotasks();
  }
  const result = await resultPromise;

  assert.equal(result.ok, true);
  assert.equal(result.reason, "empty-queue");
});

test("requestLicenseApi sets a timeout and abort signal for slow license requests", async () => {
  let receivedSignal = null;
  const { exports, timers } = loadBackgroundScript({
    fetchImpl: (_url, options) => {
      receivedSignal = options?.signal || null;
      return new Promise((_resolve, reject) => {
        if (receivedSignal) {
          receivedSignal.addEventListener("abort", () => {
            reject(new Error("aborted"));
          }, { once: true });
        }
      });
    }
  });

  const pendingRequest = exports.requestLicenseApi("/license/validate", { code: "ZS-Y-TEST" });
  await flushMicrotasks();

  assert.ok(receivedSignal);
  assert.ok(timers.length > 0);
  timers[0].fn();

  await assert.rejects(pendingRequest, /超时|timeout|aborted/i);
});

test("downloadCsv prefixes the exported file with a UTF-8 BOM", async () => {
  const activeLicense = {
    status: "active",
    isActive: true,
    message: "授权有效。"
  };
  const { exports, createdUrls } = loadDashboardScript({
    responses: [
      { ok: true, license: activeLicense },
      { ok: true, leads: [] }
    ]
  });

  await flushMicrotasks();
  exports.setState({
    license: activeLicense,
    leads: [
      {
        name: "测试商家",
        phone: "123456",
        query: "门窗"
      }
    ]
  });

  exports.downloadCsv();

  assert.equal(createdUrls.length, 1);
  const csvBytes = new Uint8Array(await createdUrls[0].blob.arrayBuffer());
  assert.deepEqual([...csvBytes.slice(0, 3)], [0xef, 0xbb, 0xbf]);
});
