/**
 * Cross-platform persistence for Codex OAuth tokens.
 *
 *  - POSIX (macOS, Linux): `~/.sandcode/codex-auth.json` with 0600 perms,
 *    written atomically via tmp + rename.
 *  - Windows: same file under `%USERPROFILE%\.sandcode\codex-auth.json`,
 *    relying on the default user-profile ACL (inherited read/write only for
 *    the current user). We don't call `icacls` — the default is already
 *    user-scoped on modern Windows, and shelling out would require admin in
 *    some profiles.
 *
 * The in-memory cache avoids re-reading the file on every API call. It's
 * invalidated when `saveCodexTokens` is called.
 */

import { constants as fsConstants } from 'node:fs'
import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'
import { join } from 'node:path'

import { getSandcodeConfigHomeDir } from '../../utils/envUtils.js'
import type { CodexTokens } from './codexOAuth.js'

const FILENAME = 'codex-auth.json'
const FILE_VERSION = 1

interface PersistedShape {
  version: number
  tokens: CodexTokens
}

function tokenPath(): string {
  return join(getSandcodeConfigHomeDir(), FILENAME)
}

let cache: CodexTokens | null = null
let cacheLoaded = false

export async function loadCodexTokens(): Promise<CodexTokens | null> {
  if (cacheLoaded) return cache
  try {
    const raw = await readFile(tokenPath(), 'utf8')
    const parsed = JSON.parse(raw) as PersistedShape
    if (parsed && typeof parsed === 'object' && parsed.version === FILE_VERSION) {
      cache = parsed.tokens
    } else {
      cache = null
    }
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
      // Surface parse errors — a corrupt file should not silently re-login,
      // because we'd lose the old refresh_token.
      throw new Error(
        `Could not read Codex auth file at ${tokenPath()}: ${(err as Error).message}`,
        { cause: err },
      )
    }
    cache = null
  }
  cacheLoaded = true
  return cache
}

export async function saveCodexTokens(tokens: CodexTokens): Promise<void> {
  const dir = getSandcodeConfigHomeDir()
  await mkdir(dir, { recursive: true })
  const payload: PersistedShape = { version: FILE_VERSION, tokens }
  const target = tokenPath()
  const tmp = `${target}.tmp-${process.pid}`

  // Write to tmp file first so a crash can't leave a half-written auth file.
  await writeFile(tmp, JSON.stringify(payload, null, 2), { encoding: 'utf8', mode: 0o600 })
  if (process.platform !== 'win32') {
    // writeFile's mode is advisory on some platforms — chmod to be sure.
    await chmod(tmp, 0o600).catch(() => {})
  }
  await rename(tmp, target)

  cache = tokens
  cacheLoaded = true
}

export async function clearCodexTokens(): Promise<void> {
  cache = null
  cacheLoaded = true
  try {
    await unlink(tokenPath())
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err
  }
}

/** Force next `loadCodexTokens()` to re-read from disk (for tests). */
export function resetCodexTokenCache(): void {
  cache = null
  cacheLoaded = false
}

/** Sanity check used by status commands. */
export async function codexAuthFileExists(): Promise<boolean> {
  const { access } = await import('node:fs/promises')
  try {
    await access(tokenPath(), fsConstants.R_OK)
    return true
  } catch {
    return false
  }
}

export function getCodexAuthFilePath(): string {
  return tokenPath()
}
