const JANUS_URL = process.env.JANUS_URL!;
const CLIENT_ID = process.env.HUMAND_CLIENT_ID!;
const CLIENT_SECRET = process.env.HUMAND_CLIENT_SECRET!;
const REDIRECT_URI = process.env.REDIRECT_URI!;

const COOKIE_ACCESS = 'hu_access_token';
const COOKIE_REFRESH = 'hu_refresh_token';
const COOKIE_OPTS = `HttpOnly; ${process.env.NODE_ENV === 'production' ? 'Secure; ' : ''}SameSite=Lax; Path=/`;

// ── Janus calls ──────────────────────────────────────────────────────────────

export interface TokenSet {
  access_token: string;
  refresh_token: string;
  expires_in: number;
}

export async function exchangeCode(
  code: string,
  codeVerifier: string,
): Promise<TokenSet> {
  return callJanusToken(
    new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      code_verifier: codeVerifier,
      redirect_uri: REDIRECT_URI,
    }),
  );
}

export async function refreshTokens(refreshToken: string): Promise<TokenSet> {
  return callJanusToken(
    new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
    }),
  );
}

async function callJanusToken(body: URLSearchParams): Promise<TokenSet> {
  const credentials = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10_000);

  if (!JANUS_URL) {
    throw new Error('JANUS_URL is not set');
  }

  try {
    const requestData = {
      method: 'POST',
      headers: {
        Authorization: `Basic ${credentials}`,
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: body.toString(),
      signal: controller.signal,
    };

    const res = await fetch(`${JANUS_URL}/oauth2/token`, requestData);

    if (!res.ok) {
      const body = await res.json();
      throw new Error(`Janus ${res.status}: ${body.error}`);
    }

    return res.json() as Promise<TokenSet>;
  } finally {
    clearTimeout(timeout);
  }
}

// ── Cookie helpers ────────────────────────────────────────────────────────────

export function buildTokenCookies(tokens: TokenSet): string[] {
  const accessMaxAge = tokens.expires_in ?? 900;
  const refreshMaxAge = 60 * 60 * 24 * 30; // 30 days

  return [
    `${COOKIE_ACCESS}=${tokens.access_token}; Max-Age=${accessMaxAge}; ${COOKIE_OPTS}`,
    `${COOKIE_REFRESH}=${tokens.refresh_token}; Max-Age=${refreshMaxAge}; ${COOKIE_OPTS}`,
  ];
}

export function buildClearCookies(): string[] {
  return [
    `${COOKIE_ACCESS}=; Max-Age=0; ${COOKIE_OPTS}`,
    `${COOKIE_REFRESH}=; Max-Age=0; ${COOKIE_OPTS}`,
  ];
}

export function parseCookies(req: {
  headers: { cookie?: string | string[] };
}): Record<string, string> {
  const raw = req.headers.cookie;
  const header = Array.isArray(raw) ? raw[0] : (raw ?? '');
  return Object.fromEntries(
    header.split(';').flatMap(pair => {
      const [k, ...v] = pair.trim().split('=');
      return k ? [[k.trim(), v.join('=').trim()]] : [];
    }),
  );
}

// ── JWT decode (no signature verification) ───────────────────────────────────

export interface JwtPayload {
  sub: string;
  email?: string;
  name?: string;
  picture?: string;
  exp?: number;
}

function decodeJwt(token: string): JwtPayload {
  const payload = token.split('.')[1];
  if (!payload) throw new Error('Invalid JWT');
  return JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
}

function isExpired(payload: JwtPayload): boolean {
  return !!payload.exp && payload.exp * 1000 < Date.now();
}

// ── Session resolution ────────────────────────────────────────────────────────

export interface Session {
  user: JwtPayload;
  accessToken: string;
  /** Set when tokens were silently renewed — caller must forward these Set-Cookie headers */
  renewedCookies?: string[];
}

/**
 * Resolves the current session from cookies.
 * Silently renews access_token if expired and refresh_token is present.
 * Returns null if unauthenticated.
 */
export async function resolveSession(req: {
  headers: { cookie?: string | string[] };
}): Promise<Session | null> {
  const cookies = parseCookies(req);
  const accessToken = cookies[COOKIE_ACCESS];
  const refreshToken = cookies[COOKIE_REFRESH];

  if (accessToken) {
    try {
      const payload = decodeJwt(accessToken);
      if (!isExpired(payload)) return { user: payload, accessToken };
    } catch {
      // malformed token — fall through to refresh
    }
  }

  if (!refreshToken) return null;

  try {
    const tokens = await refreshTokens(refreshToken);
    const payload = decodeJwt(tokens.access_token);
    return {
      user: payload,
      accessToken: tokens.access_token,
      renewedCookies: buildTokenCookies(tokens),
    };
  } catch {
    return null;
  }
}
