import { getOauthConfig } from '../constants/oauth.js'

/**
 * Trim wrappers / pick the real callback line when users paste chat text,
 * markdown quotes, or multiple lines (e.g. hint + URL).
 */
export function normalizeOAuthManualPasteRaw(raw: string): string {
  let s = raw.trim()
  if (!s) {
    return s
  }
  if (
    (s.startsWith('"') && s.endsWith('"')) ||
    (s.startsWith("'") && s.endsWith("'")) ||
    (s.startsWith('`') && s.endsWith('`'))
  ) {
    s = s.slice(1, -1).trim()
  }
  const lines = s.split(/\r?\n/).map(l => l.trim()).filter(Boolean)
  if (lines.length > 1) {
    const callbackLine = lines.find(
      l =>
        /\/oauth\/code\/callback\?[^\s]*code=/i.test(l) ||
        /127\.0\.0\.1:\d+\/callback\?/i.test(l) ||
        (/callback\?[^\s]*code=/i.test(l) && !/[?&]code=true\b/.test(l)),
    )
    if (callbackLine) {
      return callbackLine
    }
  }
  const urlMatch = s.match(/\bhttps?:\/\/[^\s"'<>]+/)
  if (urlMatch) {
    return urlMatch[0].replace(/[),.;]+$/, '')
  }
  return s
}

export type ParsedOAuthManualPaste = {
  authorizationCode: string
  state: string
  /**
   * When set, token exchange must use this for `redirect_uri` selection
   * (must match the redirect_uri from the authorize step).
   */
  useManualRedirectForTokenExchange?: boolean
}

/**
 * RiOS authorize URLs append `code=true` as a flow flag — not an OAuth authorization code.
 */
function isRiOsAuthorizePlaceholderCode(code: string): boolean {
  return code === 'true'
}

function inferUseManualRedirectForExchange(u: URL): boolean | undefined {
  const host = u.hostname.toLowerCase()
  if (
    host === '127.0.0.1' ||
    host === 'localhost' ||
    host === '[::1]'
  ) {
    if (u.pathname.includes('callback')) {
      return false
    }
  }

  const manual = new URL(getOauthConfig().MANUAL_REDIRECT_URL)
  const norm = (p: string) =>
    (p.endsWith('/') ? p.slice(0, -1) : p) || '/'
  if (
    u.origin === manual.origin &&
    norm(u.pathname) === norm(manual.pathname)
  ) {
    return true
  }

  return undefined
}

function attachRedirectHint(
  parsed: { authorizationCode: string; state: string },
  sourceForHint: string,
): ParsedOAuthManualPaste {
  try {
    const u = new URL(sourceForHint.trim())
    const hint = inferUseManualRedirectForExchange(u)
    return hint === undefined
      ? parsed
      : { ...parsed, useManualRedirectForTokenExchange: hint }
  } catch {
    return parsed
  }
}

/**
 * Parse pasted OAuth callback input from the manual (browser) flow.
 * Supports full URL (?code=&state=), query-only, hash fragment, and code#state.
 */
export function parseOAuthManualPaste(
  raw: string,
): ParsedOAuthManualPaste | null {
  const trimmed = normalizeOAuthManualPasteRaw(raw)
  if (!trimmed) return null

  const fromParams = (params: URLSearchParams, sourceForHint: string) => {
    const code = params.get('code')
    if (!code) return null
    if (isRiOsAuthorizePlaceholderCode(code)) return null
    return attachRedirectHint(
      {
        authorizationCode: code,
        state: params.get('state') ?? '',
      },
      sourceForHint,
    )
  }

  try {
    const u = new URL(trimmed)
    let parsed = fromParams(u.searchParams, trimmed)
    if (parsed) return parsed
    if (u.hash.length > 1) {
      parsed = fromParams(new URLSearchParams(u.hash.slice(1)), trimmed)
      if (parsed) return parsed
    }
  } catch {
    // not an absolute URL
  }

  const queryPart = trimmed.includes('?')
    ? trimmed.slice(trimmed.indexOf('?'))
    : trimmed.startsWith('code=')
      ? `?${trimmed}`
      : null
  if (queryPart) {
    const qs = queryPart.startsWith('?') ? queryPart.slice(1) : queryPart
    const parsed = fromParams(new URLSearchParams(qs), trimmed)
    if (parsed) return parsed
  }

  const hashIdx = trimmed.indexOf('#')
  if (hashIdx !== -1) {
    const authorizationCode = trimmed.slice(0, hashIdx).trim()
    const state = trimmed.slice(hashIdx + 1).trim()
    if (authorizationCode && state) {
      if (isRiOsAuthorizePlaceholderCode(authorizationCode)) return null
      return { authorizationCode, state }
    }
  }

  return null
}
