import { describe, it, expect, beforeEach, vi } from 'vitest'
import { APP_STORAGE_KEYS, LEGACY_APP_STORAGE_KEYS } from '../constants/appStorage'
import type { VaultConfig } from '../types'
import { migrateLocalStorageToVaultConfig } from './configMigration'

function makeConfig(overrides: Partial<VaultConfig> = {}): VaultConfig {
  return {
    zoom: null,
    view_mode: null,
    editor_mode: null,
    tag_colors: null,
    status_colors: null,
    property_display_modes: null,
    ...overrides,
  }
}

describe('migrateLocalStorageToVaultConfig', () => {
  let store: Record<string, string>

  beforeEach(() => {
    store = {}
    vi.stubGlobal('localStorage', {
      getItem: vi.fn((key: string) => store[key] ?? null),
      setItem: vi.fn((key: string, val: string) => { store[key] = val }),
      removeItem: vi.fn((key: string) => { delete store[key] }),
    })
  })

  // 1. Fresh install, no localStorage data
  it('returns default config unchanged when localStorage is empty', () => {
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result).toEqual(makeConfig())
  })

  // 2. Migration flag already set — idempotent
  it('returns config unchanged when migration flag is already set', () => {
    store[APP_STORAGE_KEYS.configMigrationFlag] = '1'
    store[APP_STORAGE_KEYS.zoom] = '120'

    const config = makeConfig()
    const result = migrateLocalStorageToVaultConfig(config)
    expect(result.zoom).toBeNull()
  })

  // 3. Zoom migration — string percentage to decimal fraction
  it.each([
    ['80', 0.8],
    ['100', 1.0],
    ['150', 1.5],
  ])('migrates zoom "%s" → %s', (raw, expected) => {
    store[APP_STORAGE_KEYS.zoom] = raw
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.zoom).toBe(expected)
  })

  it('ignores invalid zoom values', () => {
    store[APP_STORAGE_KEYS.zoom] = 'banana'
    expect(migrateLocalStorageToVaultConfig(makeConfig()).zoom).toBeNull()

    store[APP_STORAGE_KEYS.zoom] = '50'
    expect(migrateLocalStorageToVaultConfig(makeConfig()).zoom).toBeNull()

    store[APP_STORAGE_KEYS.zoom] = '200'
    expect(migrateLocalStorageToVaultConfig(makeConfig()).zoom).toBeNull()
  })

  // 4. View mode migration
  it.each(['editor-only', 'editor-list', 'all'])('migrates view mode "%s"', (mode) => {
    store[APP_STORAGE_KEYS.viewMode] = mode
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.view_mode).toBe(mode)
  })

  it('ignores invalid view mode strings', () => {
    store[APP_STORAGE_KEYS.viewMode] = 'split-screen'
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.view_mode).toBeNull()
  })

  // 5. Tag colors migration
  it('migrates populated tag colors', () => {
    store[APP_STORAGE_KEYS.tagColors] = JSON.stringify({ project: '#ff0000' })
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.tag_colors).toEqual({ project: '#ff0000' })
  })

  it('ignores empty tag colors object', () => {
    store[APP_STORAGE_KEYS.tagColors] = JSON.stringify({})
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.tag_colors).toBeNull()
  })

  // 6. Status colors migration
  it('migrates populated status colors', () => {
    store[APP_STORAGE_KEYS.statusColors] = JSON.stringify({ done: '#00ff00', wip: '#ffaa00' })
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.status_colors).toEqual({ done: '#00ff00', wip: '#ffaa00' })
  })

  it('ignores empty status colors object', () => {
    store[APP_STORAGE_KEYS.statusColors] = JSON.stringify({})
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.status_colors).toBeNull()
  })

  // 7. Property display modes migration
  it('migrates populated property display modes', () => {
    store[APP_STORAGE_KEYS.propertyModes] = JSON.stringify({ tags: 'inline' })
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.property_display_modes).toEqual({ tags: 'inline' })
  })

  it('ignores empty property display modes object', () => {
    store[APP_STORAGE_KEYS.propertyModes] = JSON.stringify({})
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.property_display_modes).toBeNull()
  })

  // 8. Existing config values are NOT overwritten
  it('does not overwrite existing config values with localStorage data', () => {
    store[APP_STORAGE_KEYS.zoom] = '120'
    store[APP_STORAGE_KEYS.viewMode] = 'all'
    store[APP_STORAGE_KEYS.tagColors] = JSON.stringify({ x: '#fff' })
    store[APP_STORAGE_KEYS.statusColors] = JSON.stringify({ y: '#000' })
    store[APP_STORAGE_KEYS.propertyModes] = JSON.stringify({ z: 'compact' })

    const existing = makeConfig({
      zoom: 0.9,
      view_mode: 'editor-only',
      tag_colors: { existing: '#aaa' },
      status_colors: { existing: '#bbb' },
      property_display_modes: { existing: 'full' },
    })

    const result = migrateLocalStorageToVaultConfig(existing)
    expect(result.zoom).toBe(0.9)
    expect(result.view_mode).toBe('editor-only')
    expect(result.tag_colors).toEqual({ existing: '#aaa' })
    expect(result.status_colors).toEqual({ existing: '#bbb' })
    expect(result.property_display_modes).toEqual({ existing: 'full' })
  })

  // 10. loaded=null (no vault config file yet) — uses defaults then migrates
  it('migrates from localStorage when loaded is null', () => {
    store[APP_STORAGE_KEYS.zoom] = '110'
    store[APP_STORAGE_KEYS.viewMode] = 'editor-list'
    const result = migrateLocalStorageToVaultConfig(null)
    expect(result.zoom).toBe(1.1)
    expect(result.view_mode).toBe('editor-list')
  })

  // 11. Migration flag is set after migration
  it('sets migration flag so next call returns unchanged', () => {
    store[APP_STORAGE_KEYS.zoom] = '80'
    const first = migrateLocalStorageToVaultConfig(makeConfig())
    expect(first.zoom).toBe(0.8)
    expect(store[APP_STORAGE_KEYS.configMigrationFlag]).toBe('1')

    const second = migrateLocalStorageToVaultConfig(makeConfig())
    expect(second.zoom).toBeNull()
  })

  // 12. localStorage.getItem throws — handles gracefully
  it('returns base config when localStorage.getItem throws', () => {
    vi.stubGlobal('localStorage', {
      getItem: vi.fn(() => { throw new Error('SecurityError') }),
      setItem: vi.fn(),
    })
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result).toEqual(makeConfig())
  })

  // 13. JSON parse error in colors — skips that field
  it('skips fields with invalid JSON without affecting other migrations', () => {
    store[APP_STORAGE_KEYS.tagColors] = '{bad json'
    store[APP_STORAGE_KEYS.zoom] = '90'
    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.tag_colors).toBeNull()
    expect(result.zoom).toBe(0.9)
  })

  it('still migrates legacy Laputa storage keys when Tolaria keys are absent', () => {
    store[LEGACY_APP_STORAGE_KEYS.zoom] = '110'
    store[LEGACY_APP_STORAGE_KEYS.viewMode] = 'editor-only'

    const result = migrateLocalStorageToVaultConfig(makeConfig())
    expect(result.zoom).toBe(1.1)
    expect(result.view_mode).toBe('editor-only')
  })
})
