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

const {
  buildHotmailMailApiLatestUrl,
  extractVerificationCodeFromMessage,
  filterHotmailAccountsByUsage,
  extractVerificationCode,
  getLatestHotmailMessage,
  getHotmailBulkActionLabel,
  getHotmailListToggleLabel,
  getHotmailMailApiRequestConfig,
  getHotmailVerificationPollConfig,
  getHotmailVerificationRequestTimestamp,
  normalizeHotmailServiceMode,
  normalizeHotmailMailApiMessages,
  parseHotmailImportText,
  pickHotmailAccountForRun,
  pickVerificationMessage,
  pickVerificationMessageWithFallback,
  pickVerificationMessageWithTimeFallback,
  shouldClearHotmailCurrentSelection,
  upsertHotmailAccountInList,
} = require('../hotmail-utils.js');

test('pickHotmailAccountForRun prefers authorized account with oldest lastUsedAt', () => {
  const now = Date.UTC(2026, 3, 10, 10, 0, 0);
  const accounts = [
    {
      id: 'recent',
      email: 'recent@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-recent',
      lastUsedAt: now - 1_000,
    },
    {
      id: 'oldest',
      email: 'oldest@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-oldest',
      lastUsedAt: now - 50_000,
    },
    {
      id: 'pending',
      email: 'pending@hotmail.com',
      status: 'pending',
      refreshToken: '',
      lastUsedAt: 0,
    },
  ];

  const selected = pickHotmailAccountForRun(accounts, { now });

  assert.equal(selected.id, 'oldest');
});

test('pickHotmailAccountForRun skips used accounts but ignores legacy enabled flag', () => {
  const accounts = [
    {
      id: 'disabled',
      email: 'disabled@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-disabled',
      enabled: false,
      used: false,
      lastUsedAt: 1,
    },
    {
      id: 'used',
      email: 'used@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-used',
      enabled: true,
      used: true,
      lastUsedAt: 0,
    },
    {
      id: 'available',
      email: 'available@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-available',
      enabled: true,
      used: false,
      lastUsedAt: 2,
    },
  ];

  const selected = pickHotmailAccountForRun(accounts, {});

  assert.equal(selected.id, 'disabled');
});

test('pickHotmailAccountForRun returns null for used-only pools', () => {
  assert.equal(
    pickHotmailAccountForRun([{
      id: 'used-only',
      email: 'used-only@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-used-only',
      used: true,
      lastUsedAt: 0,
    }], {}),
    null
  );
});

test('pickHotmailAccountForRun falls back to never-used authorized account first', () => {
  const accounts = [
    {
      id: 'used',
      email: 'used@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-used',
      lastUsedAt: Date.UTC(2026, 3, 10, 9, 0, 0),
    },
    {
      id: 'fresh',
      email: 'fresh@hotmail.com',
      status: 'authorized',
      refreshToken: 'rt-fresh',
      lastUsedAt: 0,
    },
  ];

  const selected = pickHotmailAccountForRun(accounts, { now: Date.UTC(2026, 3, 10, 10, 0, 0) });

  assert.equal(selected.id, 'fresh');
});

test('upsertHotmailAccountInList replaces matching account state by id', () => {
  const accounts = [
    {
      id: 'active',
      email: 'active@hotmail.com',
      status: 'authorized',
      used: false,
    },
    {
      id: 'other',
      email: 'other@hotmail.com',
      status: 'authorized',
      used: false,
    },
  ];

  const nextAccounts = upsertHotmailAccountInList(accounts, {
    id: 'active',
    email: 'active@hotmail.com',
    status: 'authorized',
    used: true,
  });

  assert.deepEqual(nextAccounts, [
    {
      id: 'active',
      email: 'active@hotmail.com',
      status: 'authorized',
      used: true,
    },
    {
      id: 'other',
      email: 'other@hotmail.com',
      status: 'authorized',
      used: false,
    },
  ]);
});

test('shouldClearHotmailCurrentSelection returns true only when account becomes used', () => {
  assert.equal(shouldClearHotmailCurrentSelection({
    id: 'used',
    used: true,
  }), true);

  assert.equal(shouldClearHotmailCurrentSelection({
    id: 'available',
    used: false,
  }), false);
});

test('extractVerificationCode returns first six-digit code from multilingual mail text', () => {
  assert.equal(extractVerificationCode('你的 ChatGPT 验证码为 370794，请勿泄露。'), '370794');
  assert.equal(extractVerificationCode('Your verification code is 654321.'), '654321');
  assert.equal(extractVerificationCode('ChatGPT Log-in Code\nIf that was you, enter this code:\n\n982219'), '982219');
  assert.equal(extractVerificationCode('No code here'), null);
});

test('extractVerificationCodeFromMessage reads code from the latest message subject or preview', () => {
  assert.equal(
    extractVerificationCodeFromMessage({
      subject: '你的 ChatGPT 代码为 192742',
      bodyPreview: 'OpenAI 验证邮件',
      from: { emailAddress: { address: 'noreply@openai.com' } },
    }),
    '192742'
  );

  assert.equal(
    extractVerificationCodeFromMessage({
      subject: 'OpenAI security message',
      bodyPreview: 'Your verification code is 654321.',
      from: { emailAddress: { address: 'noreply@openai.com' } },
    }),
    '654321'
  );

  assert.equal(
    extractVerificationCodeFromMessage({
      subject: 'ChatGPT Log-in Code',
      bodyPreview: 'We noticed a suspicious log-in on your account. If that was you, enter this code:\n\n982219',
      from: { emailAddress: { address: 'noreply@openai.com' } },
    }),
    '982219'
  );
});

test('getHotmailListToggleLabel reflects expanded state and account count', () => {
  assert.equal(getHotmailListToggleLabel(false, 0), '展开列表');
  assert.equal(getHotmailListToggleLabel(false, 7), '展开列表（7）');
  assert.equal(getHotmailListToggleLabel(true, 7), '收起列表（7）');
});

test('filterHotmailAccountsByUsage can pick only used accounts or return all accounts', () => {
  const accounts = [
    { id: 'used-1', email: 'used-1@hotmail.com', used: true },
    { id: 'fresh-1', email: 'fresh-1@hotmail.com', used: false },
    { id: 'used-2', email: 'used-2@hotmail.com', used: true },
  ];

  assert.deepEqual(
    filterHotmailAccountsByUsage(accounts, 'used').map((account) => account.id),
    ['used-1', 'used-2']
  );

  assert.deepEqual(
    filterHotmailAccountsByUsage(accounts, 'all').map((account) => account.id),
    ['used-1', 'fresh-1', 'used-2']
  );
});

test('getHotmailBulkActionLabel reflects action type and count', () => {
  assert.equal(getHotmailBulkActionLabel('used', 0), '清空已用');
  assert.equal(getHotmailBulkActionLabel('used', 3), '清空已用（3）');
  assert.equal(getHotmailBulkActionLabel('all', 5), '全部删除（5）');
});

test('getLatestHotmailMessage picks the newest received mail', () => {
  const latest = getLatestHotmailMessage([
    {
      id: 'older',
      subject: 'older',
      receivedDateTime: '2026-04-11T00:01:00.000Z',
    },
    {
      id: 'newest',
      subject: 'newest',
      receivedDateTime: '2026-04-11T00:05:00.000Z',
    },
    {
      id: 'middle',
      subject: 'middle',
      receivedDateTime: '2026-04-11T00:03:00.000Z',
    },
  ]);

  assert.equal(latest.id, 'newest');
});

test('pickVerificationMessage filters by time, sender, subject, and excluded codes', () => {
  const messages = [
    {
      id: 'old-mail',
      subject: 'Your code is 111111',
      from: { emailAddress: { address: 'noreply@openai.com' } },
      bodyPreview: '111111',
      receivedDateTime: '2026-04-10T09:00:00.000Z',
    },
    {
      id: 'wrong-sender',
      subject: 'Your code is 222222',
      from: { emailAddress: { address: 'noreply@example.com' } },
      bodyPreview: '222222',
      receivedDateTime: '2026-04-10T10:01:00.000Z',
    },
    {
      id: 'good-mail',
      subject: 'ChatGPT verification code 333333',
      from: { emailAddress: { address: 'noreply@openai.com' } },
      bodyPreview: 'Use 333333 to continue',
      receivedDateTime: '2026-04-10T10:02:00.000Z',
    },
    {
      id: 'excluded-mail',
      subject: 'ChatGPT verification code 444444',
      from: { emailAddress: { address: 'noreply@openai.com' } },
      bodyPreview: 'Use 444444 to continue',
      receivedDateTime: '2026-04-10T10:03:00.000Z',
    },
  ];

  const match = pickVerificationMessage(messages, {
    afterTimestamp: Date.UTC(2026, 3, 10, 10, 0, 0),
    senderFilters: ['openai', 'noreply'],
    subjectFilters: ['verification', 'code', 'chatgpt'],
    excludeCodes: ['444444'],
  });

  assert.equal(match.message.id, 'good-mail');
  assert.equal(match.code, '333333');
});

test('pickVerificationMessageWithFallback no longer matches arbitrary recent mails when filters miss', () => {
  const messages = [
    {
      id: 'login-mail',
      subject: 'Use this security code to continue 555666',
      from: { emailAddress: { address: 'account-security@openai.com' } },
      bodyPreview: 'Your one-time security code is 555666',
      receivedDateTime: '2026-04-10T10:05:00.000Z',
    },
  ];

  const result = pickVerificationMessageWithFallback(messages, {
    afterTimestamp: Date.UTC(2026, 3, 10, 10, 0, 0),
    senderFilters: ['noreply'],
    subjectFilters: ['verification'],
    excludeCodes: [],
  });

  assert.equal(result.match, null);
  assert.equal(result.usedRelaxedFilters, false);
  assert.equal(result.usedTimeFallback, false);
});

test('pickVerificationMessageWithTimeFallback can ignore afterTimestamp while keeping sender and subject filters', () => {
  const messages = [
    {
      id: 'slightly-old-mail',
      subject: '你的 ChatGPT 代码为 141735',
      from: { emailAddress: { address: 'unknown' } },
      bodyPreview: 'OpenAI logo ...',
      receivedDateTime: '2026-04-10T10:00:02.000Z',
    },
  ];

  const result = pickVerificationMessageWithTimeFallback(messages, {
    afterTimestamp: Date.UTC(2026, 3, 10, 10, 0, 10),
    senderFilters: ['openai', 'noreply'],
    subjectFilters: ['verify', 'verification', 'code'],
    excludeCodes: [],
  });

  assert.equal(result.match.message.id, 'slightly-old-mail');
  assert.equal(result.match.code, '141735');
  assert.equal(result.usedRelaxedFilters, false);
  assert.equal(result.usedTimeFallback, true);
});

test('buildHotmailMailApiLatestUrl includes email, client id, refresh token, and mailbox', () => {
  const url = new URL(buildHotmailMailApiLatestUrl({
    apiUrl: 'https://example.com/api/mail-new',
    clientId: 'client-123',
    email: 'user@hotmail.com',
    refreshToken: 'refresh-token-xyz',
    mailbox: 'Junk',
  }));

  assert.equal(url.origin + url.pathname, 'https://example.com/api/mail-new');
  assert.equal(url.searchParams.get('client_id'), 'client-123');
  assert.equal(url.searchParams.get('email'), 'user@hotmail.com');
  assert.equal(url.searchParams.get('refresh_token'), 'refresh-token-xyz');
  assert.equal(url.searchParams.get('mailbox'), 'Junk');
  assert.equal(url.searchParams.get('response_type'), 'json');
});

test('buildHotmailMailApiLatestUrl requires an explicit api url', () => {
  assert.throws(
    () => buildHotmailMailApiLatestUrl({ email: 'user@hotmail.com' }),
    /Hotmail mail API URL is required/
  );
});

test('buildHotmailMailApiLatestUrl supports custom api url and can omit response_type', () => {
  const url = new URL(buildHotmailMailApiLatestUrl({
    apiUrl: 'https://example.com/custom-mail-api',
    clientId: 'client-789',
    email: 'custom@hotmail.com',
    refreshToken: 'refresh-token-custom',
    mailbox: 'Spam',
    responseType: '',
  }));

  assert.equal(url.origin + url.pathname, 'https://example.com/custom-mail-api');
  assert.equal(url.searchParams.get('client_id'), 'client-789');
  assert.equal(url.searchParams.get('email'), 'custom@hotmail.com');
  assert.equal(url.searchParams.get('refresh_token'), 'refresh-token-custom');
  assert.equal(url.searchParams.get('mailbox'), 'Spam');
  assert.equal(url.searchParams.has('response_type'), false);
});

test('normalizeHotmailMailApiMessages maps third-party payload fields into verification message shape', () => {
  const messages = normalizeHotmailMailApiMessages([
    {
      id: 'mail-1',
      from: 'noreply@openai.com',
      subject: 'ChatGPT verification code',
      text: 'Use 135790 to continue',
      date: '2026-04-10T10:02:00.000Z',
    },
    {
      message_id: 'mail-2',
      sender_email: 'alerts@example.com',
      title: 'Ignored',
      body: 'No code here',
      received_at: '2026-04-10T10:03:00.000Z',
    },
  ]);

  assert.deepEqual(messages, [
    {
      id: 'mail-1',
      subject: 'ChatGPT verification code',
      from: { emailAddress: { address: 'noreply@openai.com' } },
      bodyPreview: 'Use 135790 to continue',
      receivedDateTime: '2026-04-10T10:02:00.000Z',
    },
    {
      id: 'mail-2',
      subject: 'Ignored',
      from: { emailAddress: { address: 'alerts@example.com' } },
      bodyPreview: 'No code here',
      receivedDateTime: '2026-04-10T10:03:00.000Z',
    },
  ]);
});

test('getHotmailVerificationPollConfig gives Hotmail a slower initial wait and longer polling window', () => {
  assert.deepEqual(getHotmailVerificationPollConfig(4), {
    initialDelayMs: 5000,
    maxAttempts: 12,
    intervalMs: 5000,
    requestFreshCodeFirst: false,
    ignorePersistedLastCode: true,
  });

  assert.deepEqual(getHotmailVerificationPollConfig(7), {
    initialDelayMs: 5000,
    maxAttempts: 12,
    intervalMs: 5000,
    requestFreshCodeFirst: false,
    ignorePersistedLastCode: true,
  });
});

test('getHotmailVerificationRequestTimestamp prefers actual request timestamps with a safety buffer', () => {
  const signupRequestedAt = Date.UTC(2026, 3, 10, 12, 0, 30);
  const loginRequestedAt = Date.UTC(2026, 3, 10, 12, 5, 45);

  assert.equal(
    getHotmailVerificationRequestTimestamp(4, {
      signupVerificationRequestedAt: signupRequestedAt,
      flowStartTime: signupRequestedAt - 60_000,
    }),
    signupRequestedAt - 15_000
  );

  assert.equal(
    getHotmailVerificationRequestTimestamp(7, {
      loginVerificationRequestedAt: loginRequestedAt,
      lastEmailTimestamp: loginRequestedAt - 120_000,
      flowStartTime: loginRequestedAt - 300_000,
    }),
    loginRequestedAt - 15_000
  );
});

test('getHotmailMailApiRequestConfig defines third-party mail API request defaults', () => {
  assert.deepEqual(getHotmailMailApiRequestConfig(), {
    timeoutMs: 15000,
  });
});

test('normalizeHotmailServiceMode supports API对接 remote 模式并默认回退到本地助手', () => {
  assert.equal(normalizeHotmailServiceMode('remote'), 'remote');
  assert.equal(normalizeHotmailServiceMode('REMOTE'), 'remote');
  assert.equal(normalizeHotmailServiceMode('local'), 'local');
  assert.equal(normalizeHotmailServiceMode(''), 'local');
  assert.equal(normalizeHotmailServiceMode('unknown'), 'local');
});

test('parseHotmailImportText parses account lines in email----password----clientId----token format', () => {
  const parsed = parseHotmailImportText(`
账号----密码----ID----Token
JohnRodriguez5425@hotmail.com----nb4ta1OK----9e5f94bc-e8a4-4e73-b8be-63364c29d753----refresh-token-1
alice@hotmail.com----pass-2----client-2----refresh-token-2
  `.trim());

  assert.deepEqual(parsed, [
    {
      email: 'JohnRodriguez5425@hotmail.com',
      password: 'nb4ta1OK',
      clientId: '9e5f94bc-e8a4-4e73-b8be-63364c29d753',
      refreshToken: 'refresh-token-1',
    },
    {
      email: 'alice@hotmail.com',
      password: 'pass-2',
      clientId: 'client-2',
      refreshToken: 'refresh-token-2',
    },
  ]);
});
