import type { Command } from '../commands.js'
import type { LocalCommandCall } from '../types/command.js'
import {
  createAuthorizationFlow,
  exchangeCodeForTokens,
  startLoopbackServer,
} from '../services/oauth/codexOAuth.js'
import {
  clearCodexTokens,
  loadCodexTokens,
  saveCodexTokens,
} from '../services/oauth/codexTokenStore.js'
import { openBrowser } from '../utils/browser.js'
import { saveGlobalConfig } from '../utils/config.js'

const DEFAULT_CODEX_MODEL = 'gpt-5.4'

const call: LocalCommandCall = async (args, context) => {
  const arg = args.trim().toLowerCase()

  if (arg === 'status') {
    const tokens = await loadCodexTokens()
    if (!tokens) return { type: 'text', value: 'ChatGPT Codex: not signed in.' }
    const expiresIn = Math.max(0, tokens.expires_at - Date.now())
    const mins = Math.round(expiresIn / 60_000)
    return {
      type: 'text',
      value: [
        'ChatGPT Codex: signed in',
        `  account_id : ${tokens.account_id ?? '(unknown)'}`,
        `  expires in : ${mins} min`,
        `  issued     : ${new Date(tokens.issued_at).toLocaleString()}`,
      ].join('\n'),
    }
  }

  if (arg === 'logout' || arg === 'clear') {
    await clearCodexTokens()
    return { type: 'text', value: 'Signed out of ChatGPT Codex.' }
  }

  // Default: run the full OAuth flow.
  let server: Awaited<ReturnType<typeof startLoopbackServer>> | null = null
  try {
    const flow = createAuthorizationFlow()
    try {
      server = await startLoopbackServer()
    } catch (err) {
      const code = (err as NodeJS.ErrnoException).code
      if (code === 'EADDRINUSE') {
        return {
          type: 'text',
          value:
            'Port 1455 is already in use (another Codex sign-in may be running).\n' +
            'Close the other process and try again, or run: /login-codex logout',
        }
      }
      throw err
    }

    const opened = await openBrowser(flow.url)
    const banner = opened
      ? '→ A browser tab opened for ChatGPT sign-in. Complete the flow there.'
      : `→ Open this URL in your browser to sign in:\n  ${flow.url}`

    // We can't stream progress back to the REPL easily from a local command,
    // so we print the banner via stderr so the user sees it immediately.
    process.stderr.write(banner + '\n')

    const result = await server.waitForCode(flow.state)
    const tokens = await exchangeCodeForTokens(result.code, flow.verifier)
    await saveCodexTokens(tokens)

    // Auto-configure: persist provider + default model, seed current process
    // env, and flip the active model for this session.
    const model = DEFAULT_CODEX_MODEL
    saveGlobalConfig((current) => ({
      ...current,
      provider: 'codex',
      codexDefaultModel: model,
      env: {
        ...current.env,
        SANDCODE_USE_CODEX: '1',
        SANDCODE_DEFAULT_MODEL: model,
      },
    }))
    // Running process picks up immediately without a restart.
    process.env.SANDCODE_USE_CODEX = '1'
    process.env.SANDCODE_DEFAULT_MODEL = model
    // Flip the active session's main-loop model so the next prompt routes
    // through Codex without the user having to run /model.
    try {
      context.setAppState((s) => ({ ...s, mainLoopModel: model }))
    } catch {
      // setAppState can be missing in non-interactive / subcommand contexts.
    }

    return {
      type: 'text',
      value: [
        '✓ Signed in to ChatGPT Codex',
        `  account_id : ${tokens.account_id ?? '(none in token)'}`,
        `  provider   : codex  (saved to ~/.sandcode/config.json)`,
        `  model      : ${model}  (use /model to switch to gpt-5-codex / gpt-5)`,
        '',
        '→ Ready. /codex-usage shows remaining quota. /login-codex logout to sign out.',
      ].join('\n'),
    }
  } catch (err) {
    return {
      type: 'text',
      value: `Codex sign-in failed: ${(err as Error).message}`,
    }
  } finally {
    server?.close()
  }
}

const loginCodex = {
  type: 'local',
  name: 'login-codex',
  description: 'Sign in to ChatGPT to use gpt-5/gpt-5-codex via your subscription',
  argumentHint: '[status|logout]',
  isEnabled: () => true,
  isHidden: false,
  supportsNonInteractive: true,
  load: () => Promise.resolve({ call }),
} satisfies Command

export default loginCodex
