/**
 * test/prefs-store.test.ts — unit tests for the Projects UI prefs store.
 *
 * Covers sanitizePrefs (untrusted coercion + caps) and loadPrefsStore
 * (missing file → defaults, malformed → defaults, set/get/persist roundtrip).
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { sanitizePrefs, emptyPrefs, loadPrefsStore } from '../src/http/prefs-store.js'

describe('sanitizePrefs', () => {
  it('returns empty prefs for non-object input', () => {
    expect(sanitizePrefs(null)).toEqual({ favourites: [], collapsed: {} })
    expect(sanitizePrefs(42)).toEqual(emptyPrefs())
    expect(sanitizePrefs('x')).toEqual(emptyPrefs())
    expect(sanitizePrefs([])).toEqual(emptyPrefs())
  })

  it('keeps only string favourites and de-dups them', () => {
    const out = sanitizePrefs({ favourites: ['/a', '/a', '/b', 3, null, ''] })
    expect(out.favourites).toEqual(['/a', '/b'])
  })

  it('keeps only collapsed entries whose value is exactly true', () => {
    const out = sanitizePrefs({ collapsed: { g1: true, g2: false, g3: 'yes', g4: 1 } })
    expect(out.collapsed).toEqual({ g1: true })
  })

  it('ignores an array passed as collapsed', () => {
    expect(sanitizePrefs({ collapsed: ['g1'] }).collapsed).toEqual({})
  })

  it('caps favourites at the maximum', () => {
    const many = Array.from({ length: 600 }, (_, i) => `/p${i}`)
    expect(sanitizePrefs({ favourites: many }).favourites).toHaveLength(500)
  })

  it('drops over-long strings', () => {
    const long = 'x'.repeat(5000)
    expect(sanitizePrefs({ favourites: [long, '/ok'] }).favourites).toEqual(['/ok'])
    expect(sanitizePrefs({ collapsed: { [long]: true, ok: true } }).collapsed).toEqual({ ok: true })
  })
})

describe('loadPrefsStore', () => {
  let dir: string
  let file: string

  beforeEach(() => {
    dir = mkdtempSync(path.join(tmpdir(), 'prefs-test-'))
    file = path.join(dir, 'prefs.json')
  })
  afterEach(() => {
    rmSync(dir, { recursive: true, force: true })
  })

  it('starts empty when the file is missing', () => {
    const store = loadPrefsStore(file)
    expect(store.get()).toEqual(emptyPrefs())
  })

  it('starts empty when the file is malformed JSON', () => {
    writeFileSync(file, '{not json')
    expect(loadPrefsStore(file).get()).toEqual(emptyPrefs())
  })

  it('loads and sanitizes existing prefs', () => {
    writeFileSync(file, JSON.stringify({ favourites: ['/a', '/a'], collapsed: { g: true, h: false } }))
    expect(loadPrefsStore(file).get()).toEqual({ favourites: ['/a'], collapsed: { g: true } })
  })

  it('get() returns a fresh copy (callers cannot mutate internal state)', () => {
    const store = loadPrefsStore(file)
    store.set({ favourites: ['/a'], collapsed: {} })
    const a = store.get()
    a.favourites.push('/hacked')
    expect(store.get().favourites).toEqual(['/a'])
  })

  it('set() sanitizes untrusted input', () => {
    const store = loadPrefsStore(file)
    store.set({ favourites: ['/a', 5, ''], collapsed: { g: true, bad: 0 } })
    expect(store.get()).toEqual({ favourites: ['/a'], collapsed: { g: true } })
  })

  it('persist() writes sanitized prefs that reload identically', async () => {
    const store = loadPrefsStore(file)
    store.set({ favourites: ['/x', '/y'], collapsed: { grp: true } })
    await store.persist()
    expect(existsSync(file)).toBe(true)
    const reloaded = loadPrefsStore(file)
    expect(reloaded.get()).toEqual({ favourites: ['/x', '/y'], collapsed: { grp: true } })
    // sanity: file is valid JSON in the expected shape
    expect(JSON.parse(readFileSync(file, 'utf8'))).toEqual({
      favourites: ['/x', '/y'],
      collapsed: { grp: true },
    })
  })
})
