import * as jose from 'jose';
import fetch from 'node-fetch';
import type { EventerSession } from './types';

export interface OidcDiscovery {
  authorization_endpoint: string;
  token_endpoint: string;
  jwks_uri: string;
  issuer: string;
}

export interface OidcConfig {
  issuerUrl: string;
  clientId: string;
  clientSecret: string;
  usernameClaim?: string;
}

export async function discoverOidcConfig(issuerUrl: string): Promise<OidcDiscovery> {
  const res = await fetch(`${issuerUrl}/.well-known/openid-configuration`);
  if (!res.ok) {
    throw new Error(`OIDC discovery failed for ${issuerUrl}: ${res.status}`);
  }
  return (await res.json()) as OidcDiscovery;
}

export function buildAuthorizationUrl(
  discovery: OidcDiscovery,
  config: OidcConfig,
  redirectUri: string,
  state: string
): string {
  const url = new URL(discovery.authorization_endpoint);
  url.searchParams.set('client_id', config.clientId);
  url.searchParams.set('redirect_uri', redirectUri);
  url.searchParams.set('response_type', 'code');
  url.searchParams.set('scope', 'openid profile email');
  url.searchParams.set('state', state);
  return url.toString();
}

interface TokenResponse {
  id_token?: string;
  access_token?: string;
  error?: string;
}

export async function exchangeCodeForTokens(
  discovery: OidcDiscovery,
  config: OidcConfig,
  code: string,
  redirectUri: string
): Promise<TokenResponse> {
  const body = new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    redirect_uri: redirectUri,
    client_id: config.clientId,
    client_secret: config.clientSecret,
  });
  const res = await fetch(discovery.token_endpoint, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  });
  if (!res.ok) {
    throw new Error(`Token exchange failed: ${res.status} ${await res.text()}`);
  }
  return (await res.json()) as TokenResponse;
}

export async function verifyIdToken(
  idToken: string,
  getKey: jose.JWTVerifyGetKey,
  expectedIssuer: string,
  expectedAudience: string,
  usernameClaim = 'preferred_username'
): Promise<EventerSession> {
  const { payload } = await jose.jwtVerify(idToken, getKey, {
    issuer: expectedIssuer,
    audience: expectedAudience,
    algorithms: ['RS256'],
  });
  const username = payload[usernameClaim];
  if (typeof username !== 'string') {
    throw new Error(`id_token missing expected username claim "${usernameClaim}"`);
  }
  return {
    username,
    email: typeof payload.email === 'string' ? payload.email : undefined,
  };
}
