/**
 * Light-weight obfuscation layer over `localStorage` for secrets the app
 * persists (Jellyfin auth tokens, TMDB / Fanart API keys). The values are
 * XOR'd with a per-install salt and base64-encoded before they hit
 * storage, so the secret isn't readable as plaintext if someone opens
 * the WebView2 storage file or inspects `localStorage` via dev-tools.
 *
 * This is obscurity, not real cryptography - a determined attacker with
 * access to the storage folder can extract the salt and reverse the XOR.
 * It exists for the casual-snooping threat model: the user shares their
 * PC with someone curious, or a screenshot accidentally exposes the
 * storage panel.
 *
 * Why not a real keychain? The app is built as a portable Tauri binary
 * with `WEBVIEW2_USER_DATA_FOLDER` redirected to `./data/` next to the
 * EXE. An OS-level keychain (Windows Credential Manager, macOS Keychain)
 * would break that portability - moving the EXE folder to another
 * machine would leave the secrets behind. localStorage in the
 * redirected folder travels with the data; obfuscation makes it
 * not-immediately-readable.
 *
 * Values written through the older plaintext API are detected on read
 * and silently re-written with obfuscation applied, so existing users
 * upgrade in place.
 */

const SALT_KEY = '__ss_salt'
const ENVELOPE_PREFIX = 'ss1:'

type StoredValue = string | null

export interface SensitiveStorage {
  getItem: (key: string) => StoredValue
  setItem: (key: string, value: string) => void
  removeItem: (key: string) => void
}

function getLocalStorage(): Storage | null {
  try {
    return typeof window !== 'undefined' ? window.localStorage : null
  } catch {
    return null
  }
}

/**
 * One-shot per install. Stored in plaintext under SALT_KEY - the salt
 * alone isn't a secret; pairing it with the XOR'd payload is what makes
 * extraction non-trivial.
 */
function getOrCreateSalt(): string {
  const ls = getLocalStorage()
  if (!ls) return ''
  try {
    const existing = ls.getItem(SALT_KEY)
    if (existing && existing.length >= 16) return existing
    // Generate a 32-char hex salt. crypto.randomUUID is available in
    // every browser Tauri 2 targets and gives us 128 bits of entropy.
    const fresh = (typeof crypto !== 'undefined' && crypto.randomUUID)
      ? crypto.randomUUID().replace(/-/g, '')
      : Math.random().toString(36).slice(2).padEnd(32, '0')
    ls.setItem(SALT_KEY, fresh)
    return fresh
  } catch {
    return ''
  }
}

function xorWithSalt(input: string, salt: string): string {
  if (!salt) return input
  const out = new Array<string>(input.length)
  for (let i = 0; i < input.length; i++) {
    const c = input.charCodeAt(i) ^ salt.charCodeAt(i % salt.length)
    out[i] = String.fromCharCode(c)
  }
  return out.join('')
}

function toBase64(input: string): string {
  // btoa only accepts Latin-1; encode UTF-8 first so non-ASCII tokens
  // (rare but possible in JWT-like payloads) survive the round trip.
  try {
    return btoa(unescape(encodeURIComponent(input)))
  } catch {
    return input
  }
}

function fromBase64(input: string): string {
  try {
    return decodeURIComponent(escape(atob(input)))
  } catch {
    return input
  }
}

function obfuscate(value: string): string {
  const salt = getOrCreateSalt()
  const scrambled = xorWithSalt(value, salt)
  return ENVELOPE_PREFIX + toBase64(scrambled)
}

function deobfuscate(stored: string): string {
  if (!stored.startsWith(ENVELOPE_PREFIX)) {
    // Legacy plaintext value - return as-is. The setItem path will
    // re-write it on the next save.
    return stored
  }
  const salt = getOrCreateSalt()
  const scrambled = fromBase64(stored.slice(ENVELOPE_PREFIX.length))
  return xorWithSalt(scrambled, salt)
}

export const sensitiveStorage: SensitiveStorage = {
  getItem(key) {
    const ls = getLocalStorage()
    if (!ls) return null
    const raw = ls.getItem(key)
    if (raw == null) return null
    const value = deobfuscate(raw)
    // Migrate plaintext values to the obfuscated envelope on read so
    // existing installs upgrade transparently.
    if (!raw.startsWith(ENVELOPE_PREFIX)) {
      try { ls.setItem(key, obfuscate(value)) } catch { /* noop */ }
    }
    return value
  },
  setItem(key, value) {
    const ls = getLocalStorage()
    if (!ls) return
    try { ls.setItem(key, obfuscate(value)) } catch { /* noop */ }
  },
  removeItem(key) {
    getLocalStorage()?.removeItem(key)
  },
}

export function readSecret(key: string): StoredValue {
  return sensitiveStorage.getItem(key)
}

export function writeSecret(key: string, value: string): void {
  sensitiveStorage.setItem(key, value)
}

export function removeSecret(key: string): void {
  sensitiveStorage.removeItem(key)
}
