import { SESSION_COOKIE_NAME, SESSION_MAX_AGE_MS } from '../constants';

function b64urlEncode(bytes: Uint8Array): string {
  let bin = '';
  for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]!);
  return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

function b64urlDecodeToString(s: string): string {
  const pad = 4 - (s.length % 4);
  const b64 = (pad === 4 ? s : s + '='.repeat(pad)).replace(/-/g, '+').replace(/_/g, '/');
  const bin = atob(b64);
  return bin;
}

async function hmacSign(secret: string, message: string): Promise<Uint8Array> {
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  return new Uint8Array(
    await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(message)),
  );
}

export async function createSessionToken(sessionSecret: string): Promise<string> {
  const payload = JSON.stringify({
    exp: Date.now() + SESSION_MAX_AGE_MS,
    iat: Date.now(),
  });
  const sig = await hmacSign(sessionSecret, payload);
  return `${b64urlEncode(new TextEncoder().encode(payload))}.${b64urlEncode(sig)}`;
}

export async function verifySessionToken(
  sessionSecret: string | undefined,
  cookieHeader: string | null,
): Promise<boolean> {
  if (!sessionSecret || sessionSecret.trim().length < 8) return false;
  if (!cookieHeader) return false;
  const cookies = parseCookies(cookieHeader);
  const token = cookies[SESSION_COOKIE_NAME];
  if (!token) return false;
  const dot = token.lastIndexOf('.');
  if (dot === -1) return false;
  const payloadPart = token.slice(0, dot);
  const sigPart = token.slice(dot + 1);
  let payloadStr: string;
  try {
    payloadStr = b64urlDecodeToString(payloadPart);
  } catch {
    return false;
  }
  let sigBytes: Uint8Array;
  try {
    const pad = 4 - (sigPart.length % 4);
    const b64 = (pad === 4 ? sigPart : sigPart + '='.repeat(pad))
      .replace(/-/g, '+')
      .replace(/_/g, '/');
    const bin = atob(b64);
    sigBytes = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) sigBytes[i] = bin.charCodeAt(i)!;
  } catch {
    return false;
  }
  const expected = await hmacSign(sessionSecret, payloadStr);
  if (expected.length !== sigBytes.length) return false;
  let ok = 0;
  for (let i = 0; i < expected.length; i++) ok |= expected[i]! ^ sigBytes[i]!;
  if (ok !== 0) return false;
  try {
    const data = JSON.parse(payloadStr) as { exp?: number };
    if (typeof data.exp !== 'number' || data.exp < Date.now()) return false;
    return true;
  } catch {
    return false;
  }
}

function parseCookies(header: string): Record<string, string> {
  const out: Record<string, string> = {};
  for (const part of header.split(';')) {
    const idx = part.indexOf('=');
    if (idx === -1) continue;
    const k = part.slice(0, idx).trim();
    const v = part.slice(idx + 1).trim();
    out[k] = decodeURIComponent(v);
  }
  return out;
}

/** 根据请求 URL 判断是否为 HTTPS，用于设置 Secure Cookie */
export function sessionCookieHeaderForRequest(
  token: string,
  maxAgeSec: number,
  requestUrl: string,
): string {
  const url = new URL(requestUrl);
  const isSecure = url.protocol === 'https:';
  const parts = [
    `${SESSION_COOKIE_NAME}=${encodeURIComponent(token)}`,
    'HttpOnly',
    'Path=/',
    'SameSite=Lax',
    `Max-Age=${maxAgeSec}`,
  ];
  if (isSecure) parts.push('Secure');
  return parts.join('; ');
}

export function clearSessionCookieHeaderForRequest(requestUrl: string): string {
  const url = new URL(requestUrl);
  const isSecure = url.protocol === 'https:';
  const parts = [
    `${SESSION_COOKIE_NAME}=`,
    'HttpOnly',
    'Path=/',
    'SameSite=Lax',
    'Max-Age=0',
  ];
  if (isSecure) parts.push('Secure');
  return parts.join('; ');
}

export { SESSION_COOKIE_NAME };
