import { createHash, randomBytes } from "node:crypto";
import { googleOAuth } from "./config";

/**
 * Google sign-in via the OAuth 2.0 authorization-code flow with PKCE.
 *
 * Hand-rolled rather than pulled from a framework: the flow is three requests,
 * and doing it directly keeps the session model in this codebase's own tables
 * instead of a second, parallel notion of "user".
 */

const AUTH_ENDPOINT = "https://accounts.google.com/o/oauth2/v2/auth";
const TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
const USERINFO_ENDPOINT = "https://openidconnect.googleapis.com/v1/userinfo";

export interface PkcePair {
  verifier: string;
  challenge: string;
}

export function createPkcePair(): PkcePair {
  const verifier = randomBytes(32).toString("base64url");
  const challenge = createHash("sha256").update(verifier).digest("base64url");
  return { verifier, challenge };
}

export function createState(): string {
  return randomBytes(16).toString("base64url");
}

export function buildAuthorizationUrl(state: string, challenge: string): string {
  const params = new URLSearchParams({
    client_id: googleOAuth.clientId,
    redirect_uri: googleOAuth.redirectUri,
    response_type: "code",
    scope: "openid email profile",
    state,
    code_challenge: challenge,
    code_challenge_method: "S256",
    // Always let the user pick an account rather than silently reusing one.
    prompt: "select_account",
    access_type: "online",
  });

  return `${AUTH_ENDPOINT}?${params.toString()}`;
}

interface TokenResponse {
  access_token: string;
  expires_in: number;
  token_type: string;
  id_token?: string;
}

export async function exchangeCodeForTokens(
  code: string,
  verifier: string
): Promise<TokenResponse> {
  const response = await fetch(TOKEN_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      client_id: googleOAuth.clientId,
      client_secret: googleOAuth.clientSecret,
      code,
      code_verifier: verifier,
      grant_type: "authorization_code",
      redirect_uri: googleOAuth.redirectUri,
    }),
  });

  if (!response.ok) {
    throw new Error(`Google token exchange failed (${response.status})`);
  }
  return (await response.json()) as TokenResponse;
}

export interface GoogleProfile {
  /** Stable Google account identifier — the value we store, not the email. */
  sub: string;
  email: string;
  emailVerified: boolean;
  name: string | null;
}

export async function fetchGoogleProfile(accessToken: string): Promise<GoogleProfile> {
  const response = await fetch(USERINFO_ENDPOINT, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });

  if (!response.ok) {
    throw new Error(`Google userinfo request failed (${response.status})`);
  }

  const payload = (await response.json()) as {
    sub?: string;
    email?: string;
    email_verified?: boolean;
    name?: string;
  };

  if (!payload.sub || !payload.email) {
    throw new Error("Google profile is missing sub or email");
  }

  return {
    sub: payload.sub,
    email: payload.email,
    emailVerified: Boolean(payload.email_verified),
    name: payload.name ?? null,
  };
}
