import "server-only";
import { getSettings } from "./settings";
import { getBaseUrl } from "./email";

/**
 * Social sign-in (OAuth 2.0 / OIDC) for Google and LinkedIn.
 * Credentials come from admin System settings (oauth.* keys) with env
 * fallbacks (GOOGLE_CLIENT_ID/SECRET, LINKEDIN_CLIENT_ID/SECRET).
 * Buttons only render for configured providers.
 */

export type OAuthProviderKey = "google" | "linkedin";

export type OAuthProfile = {
  providerAccountId: string;
  email: string;
  emailVerified: boolean;
  name: string;
  picture: string | null;
};

type ProviderMeta = {
  key: OAuthProviderKey;
  label: string;
  authUrl: string;
  tokenUrl: string;
  userinfoUrl: string;
  scope: string;
};

const PROVIDERS: Record<OAuthProviderKey, ProviderMeta> = {
  google: {
    key: "google",
    label: "Google",
    authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
    tokenUrl: "https://oauth2.googleapis.com/token",
    userinfoUrl: "https://openidconnect.googleapis.com/v1/userinfo",
    scope: "openid email profile",
  },
  linkedin: {
    key: "linkedin",
    label: "LinkedIn",
    authUrl: "https://www.linkedin.com/oauth/v2/authorization",
    tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken",
    userinfoUrl: "https://api.linkedin.com/v2/userinfo",
    scope: "openid profile email",
  },
};

export function isOAuthProvider(p: string): p is OAuthProviderKey {
  return p === "google" || p === "linkedin";
}

async function credentialsFor(key: OAuthProviderKey): Promise<{ clientId: string; clientSecret: string } | null> {
  const s = await getSettings([`oauth.${key}.clientId`, `oauth.${key}.clientSecret`]);
  const clientId = s[`oauth.${key}.clientId`] || process.env[`${key.toUpperCase()}_CLIENT_ID`] || "";
  const clientSecret = s[`oauth.${key}.clientSecret`] || process.env[`${key.toUpperCase()}_CLIENT_SECRET`] || "";
  return clientId && clientSecret ? { clientId, clientSecret } : null;
}

/** Which social providers are ready to use (drives button visibility). */
export async function getEnabledOAuthProviders(): Promise<OAuthProviderKey[]> {
  const out: OAuthProviderKey[] = [];
  for (const key of ["google", "linkedin"] as const) {
    if (await credentialsFor(key)) out.push(key);
  }
  return out;
}

export async function redirectUriFor(key: OAuthProviderKey): Promise<string> {
  return `${await getBaseUrl()}/api/auth/oauth/${key}/callback`;
}

/** Build the provider authorization URL for the start of the flow. */
export async function buildAuthorizationUrl(key: OAuthProviderKey, state: string): Promise<string | null> {
  const creds = await credentialsFor(key);
  if (!creds) return null;
  const meta = PROVIDERS[key];
  const params = new URLSearchParams({
    response_type: "code",
    client_id: creds.clientId,
    redirect_uri: await redirectUriFor(key),
    scope: meta.scope,
    state,
    ...(key === "google" ? { access_type: "online", prompt: "select_account" } : {}),
  });
  return `${meta.authUrl}?${params.toString()}`;
}

/** Exchange the authorization code and fetch the user's profile. */
export async function exchangeCodeForProfile(key: OAuthProviderKey, code: string): Promise<OAuthProfile> {
  const creds = await credentialsFor(key);
  if (!creds) throw new Error(`${PROVIDERS[key].label} sign-in is not configured.`);
  const meta = PROVIDERS[key];

  const tokenRes = await fetch(meta.tokenUrl, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      client_id: creds.clientId,
      client_secret: creds.clientSecret,
      redirect_uri: await redirectUriFor(key),
    }),
    signal: AbortSignal.timeout(10_000),
  });
  if (!tokenRes.ok) {
    const detail = await tokenRes.text().catch(() => "");
    throw new Error(`${meta.label} token exchange failed (${tokenRes.status}): ${detail.slice(0, 200)}`);
  }
  const tokens = (await tokenRes.json()) as { access_token?: string };
  if (!tokens.access_token) throw new Error(`${meta.label} returned no access token.`);

  const infoRes = await fetch(meta.userinfoUrl, {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
    signal: AbortSignal.timeout(10_000),
  });
  if (!infoRes.ok) throw new Error(`${meta.label} userinfo failed (${infoRes.status}).`);
  const info = (await infoRes.json()) as {
    sub?: string; email?: string; email_verified?: boolean | string;
    name?: string; given_name?: string; family_name?: string; picture?: string;
  };
  if (!info.sub) throw new Error(`${meta.label} returned no subject id.`);
  if (!info.email) throw new Error(`${meta.label} did not share an email address — email permission is required.`);

  return {
    providerAccountId: info.sub,
    email: info.email.toLowerCase(),
    emailVerified: info.email_verified === true || info.email_verified === "true" || key === "linkedin",
    name: info.name || [info.given_name, info.family_name].filter(Boolean).join(" ") || info.email.split("@")[0],
    picture: info.picture ?? null,
  };
}

export const OAUTH_STATE_COOKIE = "confexe_oauth_state";
