import { createHash, createSign, generateKeyPairSync, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
import type { IncomingMessage, ServerResponse } from 'node:http';
import { appendFileSync, existsSync, promises as fs, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

export type AuthInfo = {
  token: string;
  clientId: string;
  scopes: string[];
  expiresAt?: number;
  resource?: URL;
  extra?: Record<string, unknown>;
};

export type AuthenticatedIncomingMessage = IncomingMessage & {
  auth?: AuthInfo;
};

type ClientRecord = {
  clientId: string;
  redirectUris: string[];
  clientName?: string;
  scope?: string;
  tokenEndpointAuthMethod: 'none';
  grantTypes: string[];
  responseTypes: string[];
  createdAt: number;
};

type AuthorizationCodeRecord = {
  code: string;
  clientId: string;
  redirectUri: string;
  codeChallenge: string;
  codeChallengeMethod: 'S256';
  nonce?: string;
  scopes: string[];
  resource: string;
  expiresAt: number;
};

type TokenRecord = {
  accessToken: string;
  refreshToken: string;
  clientId: string;
  scopes: string[];
  resource: string;
  accessTokenExpiresAt: number;
  refreshTokenExpiresAt: number;
  revoked: boolean;
};

type PersistedOAuthState = {
  version: 1;
  updatedAt: number;
  clients: ClientRecord[];
  authCodes: AuthorizationCodeRecord[];
  tokens: TokenRecord[];
};

const DEFAULT_SCOPES = ['project', 'offline_access', 'openid', 'profile', 'email'];
const ACCESS_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
const REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
const AUTH_CODE_TTL_SECONDS = 5 * 60;
const CHATGPT_REDIRECT_PREFIX = 'https://chatgpt.com/connector/oauth/';
const CHATGPT_CLIENT_NAME = 'ChatGPT';
const STATIC_CLIENT_ID = process.env.OAUTH_CLIENT_ID?.trim() || '';
const STATIC_CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET?.trim() || '';
const USE_STATIC_OAUTH_CREDENTIALS = Boolean(STATIC_CLIENT_ID && STATIC_CLIENT_SECRET);
const OAUTH_STATE_PATH = process.env.OAUTH_STATE_PATH?.trim() || path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '.oauth-state.json');
const OAUTH_AUDIT_LOG_PATH = '';
const OAUTH_VERBOSE_LOGS = false;

const OIDC_KEY_PAIR = generateKeyPairSync('rsa', { modulusLength: 2048 });
const OIDC_PRIVATE_KEY = OIDC_KEY_PAIR.privateKey;
const OIDC_PUBLIC_JWK = {
  ...OIDC_KEY_PAIR.publicKey.export({ format: 'jwk' }),
  use: 'sig',
  alg: 'RS256',
  kid: 'bent-frontend-mcp'
};

function sendJson(res: ServerResponse, statusCode: number, body: unknown, headers: Record<string, string> = {}): void {
  res.writeHead(statusCode, {
    'content-type': 'application/json; charset=utf-8',
    'cache-control': 'no-store',
    pragma: 'no-cache',
    ...headers
  });
  res.end(JSON.stringify(body));
}

function logOAuth(message: string, details?: Record<string, unknown>): void {
  const suffix = details ? ` ${JSON.stringify(details)}` : '';
  const line = `[oauth] ${message}${suffix}`;
  if (OAUTH_VERBOSE_LOGS) {
    console.log(line);
  }
  if (!OAUTH_AUDIT_LOG_PATH) {
    return;
  }
  try {
    appendFileSync(OAUTH_AUDIT_LOG_PATH, `${new Date().toISOString()} ${line}\n`, { encoding: 'utf8', mode: 0o600 });
  } catch {
    // Ignore audit logging failures.
  }
}

function getHeaderValue(value: string | string[] | undefined): string {
  if (Array.isArray(value)) {
    return value[0] ?? '';
  }
  return value ?? '';
}

function getRequestHost(req: IncomingMessage): string {
  const forwardedHost = getHeaderValue(req.headers['x-forwarded-host']);
  const host = forwardedHost || getHeaderValue(req.headers.host);
  return host.toLowerCase();
}

function getRequestProtocol(req: IncomingMessage): 'http' | 'https' {
  const forwardedProto = getHeaderValue(req.headers['x-forwarded-proto']).toLowerCase();
  if (forwardedProto === 'http' || forwardedProto === 'https') {
    return forwardedProto;
  }
  return getRequestHost(req).startsWith('localhost') || getRequestHost(req).startsWith('127.0.0.1') || getRequestHost(req).startsWith('[::1]')
    ? 'http'
    : 'https';
}

function getRequestBaseUrl(req: IncomingMessage): URL {
  const explicitBaseUrl = process.env.PUBLIC_BASE_URL?.trim();
  if (explicitBaseUrl) {
    const url = new URL(explicitBaseUrl);
    if (!url.pathname.endsWith('/')) {
      url.pathname = `${url.pathname}/`;
    }
    return url;
  }

  const host = getRequestHost(req);
  const protocol = getRequestProtocol(req);
  return new URL(`${protocol}://${host}/`);
}

function getRequestOrigin(req: IncomingMessage): string {
  const baseUrl = getRequestBaseUrl(req);
  const pathname = baseUrl.pathname.replace(/\/$/, '');
  return `${baseUrl.origin}${pathname}`;
}

function getCanonicalResourceUrl(req: IncomingMessage): URL {
  const baseUrl = getRequestBaseUrl(req);
  return new URL('mcp', baseUrl);
}

function getProtectedResourceMetadataUrl(req: IncomingMessage): string {
  const baseUrl = getRequestBaseUrl(req);
  return new URL('.well-known/oauth-protected-resource/mcp', baseUrl).toString();
}

function normalizeResourceUrl(candidate: string): string {
  const url = new URL(candidate);
  url.hash = '';
  return url.toString();
}

function readBody(req: IncomingMessage): Promise<string> {
  return new Promise((resolve, reject) => {
    let body = '';
    req.setEncoding('utf8');
    req.on('data', chunk => {
      body += String(chunk);
      if (body.length > 1_000_000) {
        reject(new Error('Request body too large.'));
        req.destroy();
      }
    });
    req.on('end', () => resolve(body));
    req.on('error', reject);
  });
}

function parseFormBody(body: string): Record<string, string> {
  const params = new URLSearchParams(body);
  const out: Record<string, string> = {};
  for (const [key, value] of params.entries()) {
    out[key] = value;
  }
  return out;
}

function timingSafeStringEquals(a: string, b: string): boolean {
  const aBuffer = Buffer.from(a);
  const bBuffer = Buffer.from(b);
  if (aBuffer.length !== bBuffer.length) {
    return false;
  }
  return timingSafeEqual(aBuffer, bBuffer);
}

function base64UrlSha256(value: string): string {
  return createHash('sha256').update(value).digest('base64url');
}

function base64UrlJson(value: unknown): string {
  return Buffer.from(JSON.stringify(value)).toString('base64url');
}

function signIdToken(payload: Record<string, unknown>): string {
  const header = { alg: 'RS256', typ: 'JWT', kid: OIDC_PUBLIC_JWK.kid };
  const unsigned = `${base64UrlJson(header)}.${base64UrlJson(payload)}`;
  const signature = createSign('RSA-SHA256').update(unsigned).sign(OIDC_PRIVATE_KEY, 'base64url');
  return `${unsigned}.${signature}`;
}

function generateToken(): string {
  return randomBytes(32).toString('base64url');
}

function parseScopeString(scope: string | undefined): string[] {
  return scope
    ? scope
        .split(/\s+/)
        .map(value => value.trim())
        .filter(Boolean)
    : [];
}

function scopesToString(scopes: string[]): string {
  return Array.from(new Set(scopes)).join(' ').trim();
}

function isSubset(scopes: string[], allowed: string[]): boolean {
  const allowedSet = new Set(allowed);
  return scopes.every(scope => allowedSet.has(scope));
}

function isChatGPTRedirectUri(candidate: string): boolean {
  try {
    const url = new URL(candidate);
    return url.origin === 'https://chatgpt.com' && url.pathname.startsWith('/connector/oauth/');
  } catch {
    return false;
  }
}

function isChatGPTConnectorRegistration(clientName: unknown, redirectUris: string[]): boolean {
  if (clientName !== CHATGPT_CLIENT_NAME) {
    return false;
  }
  return redirectUris.length > 0 && redirectUris.every(isChatGPTRedirectUri);
}

function parseBasicAuth(header: string): { username: string; password: string } | null {
  const match = header.match(/^Basic\s+(.+)$/i);
  if (!match) {
    return null;
  }

  try {
    const decoded = Buffer.from(match[1] ?? '', 'base64').toString('utf8');
    const separator = decoded.indexOf(':');
    if (separator < 0) {
      return null;
    }
    return {
      username: decoded.slice(0, separator),
      password: decoded.slice(separator + 1)
    };
  } catch {
    return null;
  }
}

export class OAuthManager {
  private readonly clients = new Map<string, ClientRecord>();
  private readonly authCodes = new Map<string, AuthorizationCodeRecord>();
  private readonly tokens = new Map<string, TokenRecord>();
  private readonly refreshTokens = new Map<string, TokenRecord>();
  private persistTail: Promise<void> = Promise.resolve();

  constructor(private readonly options: { allowedScopes?: string[] } = {}) {
    this.loadPersistedState();
  }

  get allowedScopes(): string[] {
    return this.options.allowedScopes ?? DEFAULT_SCOPES;
  }

  private pruneExpiredState(now = Date.now()): void {
    for (const [code, record] of this.authCodes) {
      if (record.expiresAt <= now) {
        this.authCodes.delete(code);
      }
    }

    for (const [token, record] of this.tokens) {
      if (record.revoked || record.refreshTokenExpiresAt <= now) {
        this.tokens.delete(token);
        this.refreshTokens.delete(record.refreshToken);
      }
    }

    for (const [refreshToken, record] of this.refreshTokens) {
      if (record.revoked || record.refreshTokenExpiresAt <= now) {
        this.refreshTokens.delete(refreshToken);
      }
    }
  }

  private reviveTokenRecord(record: TokenRecord, now = Date.now()): boolean {
    if (record.revoked || record.refreshTokenExpiresAt <= now) {
      return false;
    }
    if (record.accessTokenExpiresAt > now) {
      return false;
    }

    record.accessTokenExpiresAt = now + ACCESS_TOKEN_TTL_SECONDS * 1000;
    return true;
  }

  private loadPersistedState(): void {
    if (!existsSync(OAUTH_STATE_PATH)) {
      return;
    }

    try {
      const raw = readFileSync(OAUTH_STATE_PATH, 'utf8');
      const parsed = JSON.parse(raw) as PersistedOAuthState | null;
      if (!parsed || parsed.version !== 1) {
        return;
      }

      const now = Date.now();
      this.clients.clear();
      this.authCodes.clear();
      this.tokens.clear();
      this.refreshTokens.clear();

      for (const client of parsed.clients ?? []) {
        if (client && typeof client.clientId === 'string') {
          this.clients.set(client.clientId, client);
        }
      }

      for (const code of parsed.authCodes ?? []) {
        if (code && typeof code.code === 'string' && code.expiresAt > now) {
          this.authCodes.set(code.code, code);
        }
      }

      let changed = false;
      for (const token of parsed.tokens ?? []) {
        if (!token || typeof token.accessToken !== 'string' || typeof token.refreshToken !== 'string') {
          continue;
        }
        if (token.revoked || token.refreshTokenExpiresAt <= now) {
          continue;
        }
        if (this.reviveTokenRecord(token, now)) {
          changed = true;
        }
        this.tokens.set(token.accessToken, token);
        this.refreshTokens.set(token.refreshToken, token);
      }

      this.pruneExpiredState(now);
      if (changed) {
        void this.persistState();
      }
      logOAuth('restored oauth state', {
        clients: this.clients.size,
        authCodes: this.authCodes.size,
        tokens: this.tokens.size
      });
    } catch (error) {
      logOAuth('failed to restore oauth state', {
        path: OAUTH_STATE_PATH,
        error: error instanceof Error ? error.message : String(error)
      });
    }
  }

  private snapshotState(now = Date.now()): PersistedOAuthState {
    this.pruneExpiredState(now);
    return {
      version: 1,
      updatedAt: now,
      clients: [...this.clients.values()],
      authCodes: [...this.authCodes.values()],
      tokens: [...this.tokens.values()].filter(record => !record.revoked && record.refreshTokenExpiresAt > now)
    };
  }

  private async persistState(): Promise<void> {
    const snapshot = this.snapshotState();
    const next = this.persistTail.then(async () => {
      const tempPath = `${OAUTH_STATE_PATH}.${randomUUID()}.tmp`;
      await fs.writeFile(tempPath, JSON.stringify(snapshot, null, 2), { encoding: 'utf8', mode: 0o600 });
      await fs.rename(tempPath, OAUTH_STATE_PATH);
    });
    this.persistTail = next.catch(() => undefined);
    await next;
  }

  getResourceMetadata(req: IncomingMessage): Record<string, unknown> {
    return {
      resource: normalizeResourceUrl(getCanonicalResourceUrl(req).toString()),
      authorization_servers: [getRequestOrigin(req)],
      scopes_supported: this.allowedScopes,
      bearer_methods_supported: ['header'],
      resource_name: 'Bent frontend MCP'
    };
  }

  getAuthorizationServerMetadata(req: IncomingMessage): Record<string, unknown> {
    const baseUrl = getRequestBaseUrl(req);
    const issuer = getRequestOrigin(req);
    const metadata: Record<string, unknown> = {
      issuer,
      authorization_endpoint: new URL('oauth/authorize', baseUrl).toString(),
      token_endpoint: new URL('oauth/token', baseUrl).toString(),
      scopes_supported: this.allowedScopes,
      response_types_supported: ['code'],
      response_modes_supported: ['query'],
      grant_types_supported: ['authorization_code', 'refresh_token'],
      code_challenge_methods_supported: ['S256']
    };

    if (USE_STATIC_OAUTH_CREDENTIALS) {
      metadata.token_endpoint_auth_methods_supported = ['client_secret_basic', 'client_secret_post'];
    } else {
      metadata.registration_endpoint = new URL('oauth/register', baseUrl).toString();
      metadata.token_endpoint_auth_methods_supported = ['none'];
    }

    return metadata;
  }

  getOpenIdConfiguration(req: IncomingMessage): Record<string, unknown> {
    const baseUrl = getRequestBaseUrl(req);
    const issuer = getRequestOrigin(req);
    const metadata: Record<string, unknown> = {
      issuer,
      authorization_endpoint: new URL('oauth/authorize', baseUrl).toString(),
      token_endpoint: new URL('oauth/token', baseUrl).toString(),
      userinfo_endpoint: new URL('oauth/userinfo', baseUrl).toString(),
      jwks_uri: new URL('.well-known/jwks.json', baseUrl).toString(),
      scopes_supported: this.allowedScopes,
      response_types_supported: ['code'],
      response_modes_supported: ['query'],
      grant_types_supported: ['authorization_code', 'refresh_token'],
      subject_types_supported: ['public'],
      id_token_signing_alg_values_supported: ['RS256'],
      claims_supported: ['sub', 'iss', 'aud', 'exp', 'iat', 'nonce', 'email', 'name'],
      code_challenge_methods_supported: ['S256']
    };

    if (USE_STATIC_OAUTH_CREDENTIALS) {
      metadata.token_endpoint_auth_methods_supported = ['client_secret_basic', 'client_secret_post'];
    } else {
      metadata.registration_endpoint = new URL('oauth/register', baseUrl).toString();
      metadata.token_endpoint_auth_methods_supported = ['none'];
    }

    return metadata;
  }

  async handleWellKnownRequest(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
    const pathname = new URL(req.url ?? '/', `http://${getHeaderValue(req.headers.host) || 'localhost'}`).pathname;
    if (pathname === '/.well-known/oauth-protected-resource' || pathname === '/.well-known/oauth-protected-resource/mcp') {
      logOAuth('discovery protected-resource', { pathname });
      sendJson(res, 200, this.getResourceMetadata(req));
      return true;
    }
    if (pathname === '/.well-known/oauth-authorization-server') {
      logOAuth('discovery authorization-server', { pathname });
      sendJson(res, 200, this.getAuthorizationServerMetadata(req));
      return true;
    }
    if (pathname === '/.well-known/openid-configuration') {
      logOAuth('discovery openid-configuration', { pathname });
      sendJson(res, 200, this.getOpenIdConfiguration(req));
      return true;
    }
    if (pathname === '/.well-known/jwks.json') {
      logOAuth('discovery jwks', { pathname });
      sendJson(res, 200, { keys: [OIDC_PUBLIC_JWK] });
      return true;
    }
    return false;
  }

  async handleOAuthRequest(req: IncomingMessage, res: ServerResponse): Promise<boolean> {
    const pathname = new URL(req.url ?? '/', `http://${getHeaderValue(req.headers.host) || 'localhost'}`).pathname;
    if (pathname === '/oauth/register') {
      logOAuth('register request', { method: req.method });
      await this.handleClientRegistration(req, res);
      return true;
    }
    if (pathname === '/oauth/authorize') {
      logOAuth('authorize request', { method: req.method });
      await this.handleAuthorization(req, res);
      return true;
    }
    if (pathname === '/oauth/token') {
      logOAuth('token request', { method: req.method, contentType: getHeaderValue(req.headers['content-type']) });
      await this.handleToken(req, res);
      return true;
    }
    if (pathname === '/oauth/revoke') {
      logOAuth('revoke request', { method: req.method });
      await this.handleRevocation(req, res);
      return true;
    }
    if (pathname === '/oauth/userinfo') {
      logOAuth('userinfo request', { method: req.method });
      await this.handleUserInfo(req, res);
      return true;
    }
    return false;
  }

  authenticateRequest(req: IncomingMessage): AuthInfo | null {
    const authorization = getHeaderValue(req.headers.authorization);
    const match = authorization.match(/^Bearer\s+(.+)$/i);
    if (!match) {
      return null;
    }

    const token = match[1]?.trim();
    if (!token) {
      return null;
    }

    const record = this.tokens.get(token);
    if (!record || record.revoked) {
      return null;
    }

    const now = Date.now();
    if (record.accessTokenExpiresAt <= now) {
      if (!this.reviveTokenRecord(record, now)) {
        this.tokens.delete(token);
        return null;
      }
      void this.persistState();
    }

    const resource = getCanonicalResourceUrl(req).toString();
    if (record.resource !== resource) {
      return null;
    }

    return {
      token: record.accessToken,
      clientId: record.clientId,
      scopes: [...record.scopes],
      expiresAt: Math.floor(record.accessTokenExpiresAt / 1000),
      resource: new URL(record.resource),
      extra: {
        refreshTokenExpiresAt: record.refreshTokenExpiresAt
      }
    };
  }

  challengeResponse(req: IncomingMessage, error?: { error: string; errorDescription?: string }): Record<string, string> {
    const metadataUrl = getProtectedResourceMetadataUrl(req);
    const scope = scopesToString(this.allowedScopes);
    const challengeParts = [
      'Bearer realm="bent-frontend-mcp"',
      `resource_metadata="${metadataUrl}"`,
      `scope="${scope}"`
    ];

    if (error?.error) {
      challengeParts.push(`error="${error.error}"`);
    }
    if (error?.errorDescription) {
      challengeParts.push(`error_description="${error.errorDescription.replace(/"/g, '\\"')}"`);
    }

    return {
      'www-authenticate': challengeParts.join(', '),
      'content-type': 'application/json; charset=utf-8',
      'cache-control': 'no-store',
      pragma: 'no-cache'
    };
  }

  sendUnauthorized(req: IncomingMessage, res: ServerResponse, message = 'Authentication required', error?: { error: string; errorDescription?: string }): void {
    sendJson(res, 401, { error: 'unauthorized', message }, this.challengeResponse(req, error));
  }

  private async handleClientRegistration(req: IncomingMessage, res: ServerResponse): Promise<void> {
    if (req.method !== 'POST') {
      sendJson(res, 405, { error: 'method_not_allowed' }, { allow: 'POST' });
      return;
    }

    if (USE_STATIC_OAUTH_CREDENTIALS) {
      sendJson(res, 400, {
        error: 'invalid_client_metadata',
        error_description: 'This server uses static OAuth credentials and does not accept dynamic client registration.'
      });
      return;
    }

    const raw = await readBody(req);
    logOAuth('register body', { body: raw.slice(0, 500) });
    let payload: unknown;
    try {
      payload = raw ? JSON.parse(raw) : {};
    } catch {
      sendJson(res, 400, { error: 'invalid_client_metadata', error_description: 'Request body must be valid JSON.' });
      return;
    }

    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
      sendJson(res, 400, { error: 'invalid_client_metadata', error_description: 'Client metadata must be a JSON object.' });
      return;
    }

    const data = payload as Record<string, unknown>;
    const redirectUris = Array.isArray(data.redirect_uris)
      ? data.redirect_uris.filter((value): value is string => typeof value === 'string')
      : [];
    if (redirectUris.length === 0) {
      sendJson(res, 400, { error: 'invalid_client_metadata', error_description: 'redirect_uris is required.' });
      return;
    }
    if (!isChatGPTConnectorRegistration(data.client_name, redirectUris)) {
      sendJson(res, 400, {
        error: 'invalid_client_metadata',
        error_description: `Only ChatGPT connector registrations are allowed. redirect_uris must start with ${CHATGPT_REDIRECT_PREFIX} and client_name must be "${CHATGPT_CLIENT_NAME}".`
      });
      return;
    }

    const clientId = `client_${randomUUID()}`;
    const record: ClientRecord = {
      clientId,
      redirectUris,
      clientName: CHATGPT_CLIENT_NAME,
      scope: typeof data.scope === 'string' ? data.scope : undefined,
      tokenEndpointAuthMethod: 'none',
      grantTypes: ['authorization_code', 'refresh_token'],
      responseTypes: ['code'],
      createdAt: Math.floor(Date.now() / 1000)
    };
    this.clients.set(clientId, record);
    await this.persistState();

    sendJson(
      res,
      201,
      {
        client_id: clientId,
        client_id_issued_at: record.createdAt,
        client_secret_expires_at: 0,
        token_endpoint_auth_method: 'none',
        grant_types: record.grantTypes,
        response_types: record.responseTypes,
        redirect_uris: record.redirectUris,
        client_name: record.clientName,
        scope: record.scope ?? scopesToString(this.allowedScopes)
      },
      {
        location: new URL(`/oauth/register/${clientId}`, getRequestOrigin(req)).toString()
      }
    );
  }

  private async handleAuthorization(req: IncomingMessage, res: ServerResponse): Promise<void> {
    if (req.method !== 'GET') {
      sendJson(res, 405, { error: 'method_not_allowed' }, { allow: 'GET' });
      return;
    }

    const url = new URL(req.url ?? '/', getRequestOrigin(req));
    logOAuth('authorize query', Object.fromEntries(url.searchParams.entries()));
    const clientId = url.searchParams.get('client_id') ?? (USE_STATIC_OAUTH_CREDENTIALS ? STATIC_CLIENT_ID : 'public-client');
    const redirectUri = url.searchParams.get('redirect_uri') ?? '';
    const responseType = url.searchParams.get('response_type') ?? '';
    const state = url.searchParams.get('state') ?? '';
    const codeChallenge = url.searchParams.get('code_challenge') ?? '';
    const codeChallengeMethod = url.searchParams.get('code_challenge_method') ?? 'S256';
    const scopeParam = url.searchParams.get('scope') ?? '';
    const resourceParam = url.searchParams.get('resource') ?? '';
    const nonce = url.searchParams.get('nonce') ?? undefined;

    if (responseType !== 'code') {
      this.redirectWithOAuthError(res, redirectUri, state, 'unsupported_response_type', 'Only authorization code flow is supported.');
      return;
    }

    if (!codeChallenge || codeChallengeMethod !== 'S256') {
      this.redirectWithOAuthError(res, redirectUri, state, 'invalid_request', 'PKCE code_challenge with S256 is required.');
      return;
    }
    if (!isChatGPTRedirectUri(redirectUri)) {
      this.redirectWithOAuthError(res, redirectUri, state, 'invalid_request', 'Only ChatGPT connector redirect URIs are allowed.');
      return;
    }

    const requestedScopes = parseScopeString(scopeParam);
    const finalScopes = requestedScopes.length > 0 ? requestedScopes : [...this.allowedScopes];
    if (!isSubset(finalScopes, this.allowedScopes)) {
      this.redirectWithOAuthError(res, redirectUri, state, 'invalid_scope', 'Requested scopes are not supported.');
      return;
    }

    const resource = resourceParam ? normalizeResourceUrl(resourceParam) : getCanonicalResourceUrl(req).toString();
    if (resource !== getCanonicalResourceUrl(req).toString()) {
      this.redirectWithOAuthError(res, redirectUri, state, 'invalid_target', 'Requested resource does not match this MCP server.');
      return;
    }

    const clientRecord = this.clients.get(clientId);
    if (USE_STATIC_OAUTH_CREDENTIALS) {
      if (clientId !== STATIC_CLIENT_ID) {
        this.redirectWithOAuthError(res, redirectUri, state, 'invalid_client', 'This server only accepts the configured static ChatGPT client.');
        return;
      }
    } else if (clientRecord && (clientRecord.clientName !== CHATGPT_CLIENT_NAME || !clientRecord.redirectUris.every(isChatGPTRedirectUri))) {
      this.redirectWithOAuthError(res, redirectUri, state, 'invalid_client', 'Only ChatGPT connector clients are allowed.');
      return;
    }

    const code = generateToken();
    this.authCodes.set(code, {
      code,
      clientId,
      redirectUri,
      codeChallenge,
      codeChallengeMethod: 'S256',
      nonce,
      scopes: finalScopes,
      resource,
      expiresAt: Date.now() + AUTH_CODE_TTL_SECONDS * 1000
    });
    await this.persistState();

    const redirect = new URL(redirectUri);
    redirect.searchParams.set('code', code);
    if (state) {
      redirect.searchParams.set('state', state);
    }
    res.writeHead(302, {
      location: redirect.toString(),
      'cache-control': 'no-store',
      pragma: 'no-cache'
    });
    res.end();
  }

  private async handleToken(req: IncomingMessage, res: ServerResponse): Promise<void> {
    if (req.method !== 'POST') {
      sendJson(res, 405, { error: 'method_not_allowed' }, { allow: 'POST' });
      return;
    }

    const contentType = getHeaderValue(req.headers['content-type']).toLowerCase();
    if (!contentType.includes('application/x-www-form-urlencoded')) {
      sendJson(res, 400, { error: 'invalid_request', error_description: 'Token endpoint requires form-encoded data.' });
      return;
    }

    const form = parseFormBody(await readBody(req));
    logOAuth('token form', {
      grant_type: form.grant_type,
      has_code: Boolean(form.code),
      has_code_verifier: Boolean(form.code_verifier),
      has_redirect_uri: Boolean(form.redirect_uri),
      has_client_id: Boolean(form.client_id),
      keys: Object.keys(form)
    });
    const grantType = form.grant_type ?? '';
    const clientIdFromRequest = form.client_id ?? '';
    const basicAuth = parseBasicAuth(getHeaderValue(req.headers.authorization));
    const clientId = clientIdFromRequest || basicAuth?.username || '';
    const clientSecret = form.client_secret ?? basicAuth?.password ?? '';

    if (USE_STATIC_OAUTH_CREDENTIALS) {
      if (clientId !== STATIC_CLIENT_ID || clientSecret !== STATIC_CLIENT_SECRET) {
        sendJson(res, 401, { error: 'invalid_client', error_description: 'Invalid ChatGPT client credentials.' });
        return;
      }
    }

    if (grantType === 'authorization_code') {
      await this.exchangeAuthorizationCode(req, res, form, clientId);
      return;
    }

    if (grantType === 'refresh_token') {
      await this.exchangeRefreshToken(req, res, form, clientId);
      return;
    }

    sendJson(res, 400, { error: 'unsupported_grant_type', error_description: `Unsupported grant_type: ${grantType}` });
  }

  private async exchangeAuthorizationCode(
    req: IncomingMessage,
    res: ServerResponse,
    form: Record<string, string>,
    clientIdFromRequest: string
  ): Promise<void> {
    const code = form.code ?? '';
    const redirectUri = form.redirect_uri ?? '';
    const codeVerifier = form.code_verifier ?? '';

    if (!code || !codeVerifier) {
      sendJson(res, 400, { error: 'invalid_request', error_description: 'code and code_verifier are required.' });
      return;
    }

    const record = this.authCodes.get(code);
    if (!record) {
      sendJson(res, 400, { error: 'invalid_grant', error_description: 'Unknown authorization code.' });
      return;
    }

    if (USE_STATIC_OAUTH_CREDENTIALS) {
      if (record.clientId !== STATIC_CLIENT_ID) {
        sendJson(res, 400, { error: 'invalid_grant', error_description: 'Authorization code is not for the configured ChatGPT client.' });
        return;
      }
    } else {
      const clientRecord = this.clients.get(record.clientId);
      if (!clientRecord || clientRecord.clientName !== CHATGPT_CLIENT_NAME || !clientRecord.redirectUris.every(isChatGPTRedirectUri)) {
        sendJson(res, 400, { error: 'invalid_grant', error_description: 'Only ChatGPT connector clients are allowed.' });
        return;
      }
    }

    if (record.expiresAt <= Date.now()) {
      this.authCodes.delete(code);
      sendJson(res, 400, { error: 'invalid_grant', error_description: 'Authorization code expired.' });
      return;
    }

    if (redirectUri && record.redirectUri !== redirectUri) {
      sendJson(res, 400, { error: 'invalid_grant', error_description: 'Authorization code does not match the client or redirect_uri.' });
      return;
    }

    if (record.codeChallengeMethod !== 'S256' || !timingSafeStringEquals(base64UrlSha256(codeVerifier), record.codeChallenge)) {
      sendJson(res, 400, { error: 'invalid_grant', error_description: 'PKCE verification failed.' });
      return;
    }

    this.authCodes.delete(code);
    await this.issueTokens(req, res, record.clientId, record.scopes, record.resource, record.nonce);
  }

  private async exchangeRefreshToken(
    req: IncomingMessage,
    res: ServerResponse,
    form: Record<string, string>,
    clientIdFromRequest: string
  ): Promise<void> {
    const refreshToken = form.refresh_token ?? '';
    if (!refreshToken) {
      sendJson(res, 400, { error: 'invalid_request', error_description: 'refresh_token is required.' });
      return;
    }

    const record = this.refreshTokens.get(refreshToken);
    if (!record || record.revoked) {
      sendJson(res, 400, { error: 'invalid_grant', error_description: 'Unknown refresh token.' });
      return;
    }

    if (USE_STATIC_OAUTH_CREDENTIALS) {
      if (record.clientId !== STATIC_CLIENT_ID) {
        sendJson(res, 400, { error: 'invalid_grant', error_description: 'Refresh token is not for the configured ChatGPT client.' });
        return;
      }
    } else {
      const clientRecord = this.clients.get(record.clientId);
      if (!clientRecord || clientRecord.clientName !== CHATGPT_CLIENT_NAME || !clientRecord.redirectUris.every(isChatGPTRedirectUri)) {
        sendJson(res, 400, { error: 'invalid_grant', error_description: 'Only ChatGPT connector clients are allowed.' });
        return;
      }
    }

    if (record.refreshTokenExpiresAt <= Date.now()) {
      this.refreshTokens.delete(refreshToken);
      sendJson(res, 400, { error: 'invalid_grant', error_description: 'Refresh token expired.' });
      return;
    }

    const requestedScopes = parseScopeString(form.scope);
    const finalScopes = requestedScopes.length > 0 ? requestedScopes : record.scopes;
    if (!isSubset(finalScopes, this.allowedScopes)) {
      sendJson(res, 400, { error: 'invalid_scope', error_description: 'Requested scopes are not supported.' });
      return;
    }

    record.revoked = true;
    this.refreshTokens.delete(refreshToken);
    this.tokens.delete(record.accessToken);
    await this.issueTokens(req, res, record.clientId, finalScopes, record.resource, undefined);
  }

  private async issueTokens(
    req: IncomingMessage,
    res: ServerResponse,
    clientId: string,
    scopes: string[],
    resource: string,
    nonce?: string
  ): Promise<void> {
    const accessToken = generateToken();
    const refreshToken = generateToken();
    const now = Date.now();
    const record: TokenRecord = {
      accessToken,
      refreshToken,
      clientId,
      scopes,
      resource,
      accessTokenExpiresAt: now + ACCESS_TOKEN_TTL_SECONDS * 1000,
      refreshTokenExpiresAt: now + REFRESH_TOKEN_TTL_SECONDS * 1000,
      revoked: false
    };

    this.tokens.set(accessToken, record);
    this.refreshTokens.set(refreshToken, record);
    await this.persistState();

    const response: Record<string, unknown> = {
      access_token: accessToken,
      token_type: 'Bearer',
      expires_in: ACCESS_TOKEN_TTL_SECONDS,
      scope: scopesToString(scopes),
      refresh_token: refreshToken
    };

    if (scopes.includes('openid')) {
      const nowSeconds = Math.floor(now / 1000);
      response.id_token = signIdToken({
        iss: getRequestOrigin(req),
        sub: 'chatgpt-user',
        aud: clientId,
        exp: nowSeconds + ACCESS_TOKEN_TTL_SECONDS,
        iat: nowSeconds,
        ...(nonce ? { nonce } : {}),
        email: 'user@example.invalid',
        name: 'ChatGPT User'
      });
    }

    sendJson(
      res,
      200,
      response,
      {
        'cache-control': 'no-store',
        pragma: 'no-cache'
      }
    );
  }

  private async handleRevocation(req: IncomingMessage, res: ServerResponse): Promise<void> {
    if (req.method !== 'POST') {
      sendJson(res, 405, { error: 'method_not_allowed' }, { allow: 'POST' });
      return;
    }

    const contentType = getHeaderValue(req.headers['content-type']).toLowerCase();
    if (!contentType.includes('application/x-www-form-urlencoded')) {
      sendJson(res, 400, { error: 'invalid_request', error_description: 'Revocation endpoint requires form-encoded data.' });
      return;
    }

    const form = parseFormBody(await readBody(req));
    logOAuth('revoke form', { keys: Object.keys(form) });
    const token = form.token ?? '';
    if (!token) {
      sendJson(res, 400, { error: 'invalid_request', error_description: 'token is required.' });
      return;
    }

    const access = this.tokens.get(token);
    if (access) {
      access.revoked = true;
    this.tokens.delete(token);
    }
    const refresh = this.refreshTokens.get(token);
    if (refresh) {
      refresh.revoked = true;
      this.refreshTokens.delete(token);
    }

    await this.persistState();

    res.writeHead(200, {
      'cache-control': 'no-store',
      pragma: 'no-cache'
    });
    res.end();
  }

  private async handleUserInfo(req: IncomingMessage, res: ServerResponse): Promise<void> {
    if (req.method !== 'GET') {
      sendJson(res, 405, { error: 'method_not_allowed' }, { allow: 'GET' });
      return;
    }

    const auth = this.authenticateRequest(req);
    if (!auth) {
      sendJson(res, 401, { error: 'invalid_token', error_description: 'Missing or invalid bearer token.' }, this.challengeResponse(req));
      return;
    }

    const response: Record<string, unknown> = {
      sub: 'chatgpt-user'
    };

    if (auth.scopes.includes('email')) {
      response.email = 'user@example.invalid';
    }
    if (auth.scopes.includes('profile')) {
      response.name = 'ChatGPT User';
    }

    sendJson(res, 200, response, {
      'cache-control': 'no-store',
      pragma: 'no-cache'
    });
  }

  private redirectWithOAuthError(
    res: ServerResponse,
    redirectUri: string,
    state: string,
    error: string,
    description: string
  ): void {
    if (!redirectUri) {
      sendJson(res, 400, { error, error_description: description });
      return;
    }

    const redirect = new URL(redirectUri);
    redirect.searchParams.set('error', error);
    redirect.searchParams.set('error_description', description);
    if (state) {
      redirect.searchParams.set('state', state);
    }
    res.writeHead(302, {
      location: redirect.toString(),
      'cache-control': 'no-store',
      pragma: 'no-cache'
    });
    res.end();
  }
}
