/**
 * OAuth token storage for DimCode CLI.
 *
 * Manages OAuth credentials in ~/.dimcode/dimcode/auth.json with secure file permissions.
 */

import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { getDimcodeUserDir } from './config'

export const OAUTH_DUMMY_KEY = 'dimcode-oauth-dummy-key'

export type OAuthProviderId = 'next-api-oauth'

export interface NextApiUserInfo {
  sub: string
  email?: string
  name?: string
  preferred_username?: string
  fetched_at: number
}

export interface OAuthTokens {
  type: 'oauth'
  refresh: string
  access: string
  expires: number
  accountId?: string
  email?: string
  issuer?: string
  authorizationEndpoint?: string
  tokenEndpoint?: string
  clientId?: string
  clientSecret?: string
  scope?: string
  relayBaseUrl?: string
}

export interface AuthData {
  nextApiOauth?: OAuthTokens
}

async function getAuthFilePath(): Promise<string> {
  const dir = await getDimcodeUserDir()
  return path.join(dir, 'auth.json')
}

async function getUserInfoFilePath(): Promise<string> {
  const dir = await getDimcodeUserDir()
  return path.join(dir, 'userinfo.json')
}

function getAuthDataKey(_provider: OAuthProviderId): keyof AuthData {
  return 'nextApiOauth'
}

async function writeAuthData(filePath: string, data: AuthData): Promise<void> {
  const dir = path.dirname(filePath)
  await mkdir(dir, { recursive: true })

  const tmp = `${filePath}.tmp.${Date.now()}`
  const json = `${JSON.stringify(data, null, 2)}\n`
  await writeFile(tmp, json, { encoding: 'utf8', mode: 0o600 })

  const { rename } = await import('node:fs/promises')
  await rename(tmp, filePath)

  try {
    await chmod(filePath, 0o600)
  }
  catch {
    // Ignore chmod errors
  }
}

/**
 * Load all auth data from disk.
 */
export async function loadAuthData(): Promise<AuthData> {
  try {
    const filePath = await getAuthFilePath()
    const raw = await readFile(filePath, 'utf8')
    const parsed = JSON.parse(raw)
    return parsed && typeof parsed === 'object' ? parsed : {}
  }
  catch {
    return {}
  }
}

/**
 * Get OAuth tokens for a specific provider.
 */
export async function getOAuthTokens(
  provider: OAuthProviderId,
): Promise<OAuthTokens | undefined> {
  const data = await loadAuthData()
  return data[getAuthDataKey(provider)]
}

/**
 * Save OAuth tokens for a specific provider.
 */
export async function setOAuthTokens(
  provider: OAuthProviderId,
  tokens: OAuthTokens,
): Promise<void> {
  const filePath = await getAuthFilePath()
  const data = await loadAuthData()
  data[getAuthDataKey(provider)] = tokens
  await writeAuthData(filePath, data)
}

/**
 * Remove OAuth tokens for a specific provider.
 */
export async function removeOAuthTokens(
  provider: OAuthProviderId,
): Promise<void> {
  const filePath = await getAuthFilePath()
  const data = await loadAuthData()
  delete data[getAuthDataKey(provider)]
  await writeAuthData(filePath, data)
}

/**
 * Save user info to ~/.dimcode/dimcode/userinfo.json.
 */
export async function saveUserInfo(
  userInfo: NextApiUserInfo,
): Promise<void> {
  const filePath = await getUserInfoFilePath()
  const dir = path.dirname(filePath)
  await mkdir(dir, { recursive: true })

  const tmp = `${filePath}.tmp.${Date.now()}`
  const json = `${JSON.stringify(userInfo, null, 2)}\n`
  await writeFile(tmp, json, { encoding: 'utf8', mode: 0o600 })

  const { rename } = await import('node:fs/promises')
  await rename(tmp, filePath)

  try {
    await chmod(filePath, 0o600)
  }
  catch {
    // Ignore chmod errors
  }
}

/**
 * Load user info from ~/.dimcode/dimcode/userinfo.json.
 */
export async function loadUserInfo(): Promise<NextApiUserInfo | undefined> {
  try {
    const filePath = await getUserInfoFilePath()
    const raw = await readFile(filePath, 'utf8')
    const parsed = JSON.parse(raw)
    return parsed && typeof parsed === 'object' && parsed.sub ? parsed : undefined
  }
  catch {
    return undefined
  }
}

/**
 * Remove user info file.
 */
export async function removeUserInfo(): Promise<void> {
  try {
    const filePath = await getUserInfoFilePath()
    const { unlink } = await import('node:fs/promises')
    await unlink(filePath)
  }
  catch {
    // Ignore if file doesn't exist
  }
}
