/**
 * Round-trip + failure-mode tests for `services/authCacheStore.js`.
 *
 * The store is the disk half of the marketplace-publish fix: without
 * it, a server restart wipes the JWT cache and the FE's polling
 * fallback gets `{ user: null, jwt: null }` so any browser without the
 * token already in localStorage 401s on publish.
 *
 * These tests exercise the real cipher against a tmp directory by
 * pointing `getUserDataPaths` at it via the harness below.
 */

import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import crypto from 'crypto';

// Mock userDataDir BEFORE importing the store so the dynamic import
// below picks up the mock. Each test resets the tmpdir.
const tmpRoot = path.join(os.tmpdir(), 'authCacheStore-test-' + crypto.randomBytes(6).toString('hex'));

jest.unstable_mockModule('../../utilities/userDataDir.js', () => ({
  getUserDataPaths: () => ({ settings: tmpRoot }),
  ensureUserDataDirs: async () => { await fs.mkdir(tmpRoot, { recursive: true }); },
}));

const { default: AuthCacheStore } = await import('../authCacheStore.js');

const SAMPLE_CACHE = Object.freeze({
  user: { id: 'u-1', email: 'a@b.com' },
  hasApiKey: true,
  jwt: 'eyJ.test.jwt',
  timestamp: '2026-05-11T12:00:00.000Z',
});

async function cleanTmp() {
  try { await fs.rm(tmpRoot, { recursive: true, force: true }); } catch { /* ok */ }
}

describe('AuthCacheStore', () => {
  beforeEach(async () => { await cleanTmp(); });
  afterEach(async () => { await cleanTmp(); });

  test('load returns null when uninitialized (no init() call yet)', async () => {
    const store = new AuthCacheStore(null);
    expect(await store.load()).toBeNull();
  });

  test('load returns null when file does not exist', async () => {
    const store = new AuthCacheStore(null);
    await store.initialize();
    expect(await store.load()).toBeNull();
  });

  test('save then load returns the same row', async () => {
    const store = new AuthCacheStore(null);
    await store.initialize();
    await store.save(SAMPLE_CACHE);
    const loaded = await store.load();
    expect(loaded).toEqual(SAMPLE_CACHE);
  });

  test('save(null) deletes the file', async () => {
    const store = new AuthCacheStore(null);
    await store.initialize();
    await store.save(SAMPLE_CACHE);
    const filePath = path.join(tmpRoot, 'auth-cache.enc');
    await fs.access(filePath); // exists
    await store.save(null);
    await expect(fs.access(filePath)).rejects.toThrow();
  });

  test('clear() is a no-op when file does not exist', async () => {
    const store = new AuthCacheStore(null);
    await store.initialize();
    await expect(store.clear()).resolves.toBeUndefined();
  });

  test('two store instances on the same machine share the key — second can read what first wrote', async () => {
    // This is the actual production scenario: restart spawns a fresh
    // AuthCacheStore that must decrypt what the previous instance saved.
    const writer = new AuthCacheStore(null);
    await writer.initialize();
    await writer.save(SAMPLE_CACHE);

    const reader = new AuthCacheStore(null);
    await reader.initialize();
    expect(await reader.load()).toEqual(SAMPLE_CACHE);
  });

  test('load returns null on corrupted ciphertext', async () => {
    const store = new AuthCacheStore(null);
    await store.initialize();
    await fs.writeFile(path.join(tmpRoot, 'auth-cache.enc'), 'this-is-not-valid-base64-ciphertext!!');
    expect(await store.load()).toBeNull();
  });

  test('load returns null on schema-version mismatch', async () => {
    const store = new AuthCacheStore(null);
    await store.initialize();
    // Hand-craft a valid ciphertext with the wrong version.
    const { encrypt } = await import('../../utilities/secretsCipher.js');
    const payload = JSON.stringify({ version: 999, cache: SAMPLE_CACHE });
    const ct = encrypt(payload, store.encryptionKey);
    await fs.writeFile(path.join(tmpRoot, 'auth-cache.enc'), ct);
    expect(await store.load()).toBeNull();
  });

  test('save survives ENOENT on directory creation between init and save (best-effort)', async () => {
    // Edge case: someone deletes the settings dir mid-flight. save()
    // should warn but not throw, so the request handler can finish.
    const store = new AuthCacheStore(null);
    await store.initialize();
    await fs.rm(tmpRoot, { recursive: true, force: true });
    await expect(store.save(SAMPLE_CACHE)).resolves.toBeUndefined();
  });
});
