import { describe, it, expect, vi } from 'vitest'
import { renderHook, act } from '@testing-library/react'
import { parseConsent } from '@platform-modules/content/privacy'
import {
  useConsent,
  createLocalStorageConsentStorage,
  type ConsentStorage,
} from './useConsent.js'

// In-memory storage double so tests never depend on jsdom localStorage timing.
function memStorage(initial: string | null = null): ConsentStorage & { value: string | null } {
  const box = { value: initial }
  return {
    value: box.value,
    load() {
      return box.value
    },
    save(v: string) {
      box.value = v
      ;(this as { value: string | null }).value = v
    },
  }
}

describe('useConsent', () => {
  it('is not ready and has no consent on first render, then becomes ready after mount', () => {
    const storage = memStorage(null)
    const { result } = renderHook(() => useConsent({ version: '1', storage }))
    // after the mount effect has run, ready flips true, consent stays null (nothing stored)
    expect(result.current.ready).toBe(true)
    expect(result.current.consent).toBeNull()
  })

  it('hydrates a version-matched stored consent', () => {
    const stored = JSON.stringify({
      categories: { necessary: true, analytics: true, marketing: false, preferences: false },
      recordedAt: '2026-06-20T00:00:00.000Z',
      version: '1',
    })
    const { result } = renderHook(() => useConsent({ version: '1', storage: memStorage(stored) }))
    expect(result.current.consent?.categories.analytics).toBe(true)
    expect(result.current.consent?.version).toBe('1')
  })

  it('treats a version-mismatched stored consent as absent (re-prompt)', () => {
    const stored = JSON.stringify({
      categories: { necessary: true, analytics: true, marketing: true, preferences: true },
      recordedAt: '2026-06-20T00:00:00.000Z',
      version: '1',
    })
    const { result } = renderHook(() => useConsent({ version: '2', storage: memStorage(stored) }))
    expect(result.current.ready).toBe(true)
    expect(result.current.consent).toBeNull()
  })

  it('treats a corrupt stored value as no-consent (fail-safe, no throw)', () => {
    const { result } = renderHook(() =>
      useConsent({ version: '1', storage: memStorage('{not valid json') }),
    )
    expect(result.current.ready).toBe(true)
    expect(result.current.consent).toBeNull()
  })

  it('acceptAll persists every category on, forces necessary, returns the state', () => {
    const storage = memStorage(null)
    const { result } = renderHook(() => useConsent({ version: '3', storage }))
    let returned!: ReturnType<typeof result.current.acceptAll>
    act(() => {
      returned = result.current.acceptAll()
    })
    expect(returned.categories).toEqual({
      necessary: true,
      analytics: true,
      marketing: true,
      preferences: true,
    })
    expect(returned.version).toBe('3')
    expect(result.current.consent?.categories.marketing).toBe(true)
    // round-trips through parseConsent (what a server/reader would do)
    expect(parseConsent(JSON.parse(storage.value as string))?.categories.analytics).toBe(true)
  })

  it('rejectAll persists only necessary on', () => {
    const storage = memStorage(null)
    const { result } = renderHook(() => useConsent({ version: '1', storage }))
    act(() => {
      result.current.rejectAll()
    })
    expect(result.current.consent?.categories).toEqual({
      necessary: true,
      analytics: false,
      marketing: false,
      preferences: false,
    })
  })

  it('save forces necessary:true even when the caller passes necessary:false', () => {
    const storage = memStorage(null)
    const { result } = renderHook(() => useConsent({ version: '1', storage }))
    act(() => {
      result.current.save({
        necessary: false,
        analytics: true,
        marketing: false,
        preferences: false,
      })
    })
    expect(result.current.consent?.categories.necessary).toBe(true)
    expect(result.current.consent?.categories.analytics).toBe(true)
  })

  it('createLocalStorageConsentStorage does not throw at construction and survives load when localStorage throws', () => {
    // NOTE: `Storage.prototype` spying is jsdom-version-dependent — localStorage is not
    // always a plain Storage instance delegating to the prototype. If this spy does NOT
    // intercept (test stays GREEN without ever throwing / `load()` never exercises the
    // catch), switch the target to the instance: `vi.spyOn(window.localStorage, 'getItem')`.
    // The assertion intent is fixed: a throwing getItem must be swallowed → `load()` returns null.
    const spy = vi.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
      throw new Error('blocked')
    })
    const storage = createLocalStorageConsentStorage('test-key')
    expect(() => storage.load()).not.toThrow()
    expect(storage.load()).toBeNull()
    spy.mockRestore()
  })
})
