import { NextResponse, type NextRequest } from 'next/server';

const COOKIE_NAME = process.env.WEB_AUTH_COOKIE_NAME?.trim() || 'ai_toy_session';

function isPublicPath(pathname: string) {
  return (
    pathname === '/login' ||
    pathname.startsWith('/login/') ||
    pathname.startsWith('/_next/') ||
    pathname.startsWith('/api/auth/') ||
    pathname.startsWith('/api/img/') ||
    pathname === '/favicon.ico' ||
    pathname === '/robots.txt' ||
    pathname === '/sitemap.xml'
  );
}

function isBrowserHtml(pathname: string) {
  return !pathname.startsWith('/api/') || pathname.startsWith('/api/gallery/') || pathname.startsWith('/api/audit/');
}

function authConfigured() {
  return Boolean(process.env.WEB_AUTH_USERNAME && process.env.WEB_AUTH_SESSION_SECRET);
}

function bytesToHex(buffer: ArrayBuffer) {
  return [...new Uint8Array(buffer)].map(byte => byte.toString(16).padStart(2, '0')).join('');
}

function safeEqual(a: string, b: string) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i += 1) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return diff === 0;
}

async function sign(body: string) {
  const secret = process.env.WEB_AUTH_SESSION_SECRET || '';
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign'],
  );
  return bytesToHex(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(body)));
}

function decodePayload(body: string) {
  const base64 = body.replace(/-/g, '+').replace(/_/g, '/');
  const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
  return JSON.parse(atob(padded)) as { u?: unknown; exp?: unknown };
}

async function verifyToken(token?: string) {
  if (!authConfigured() || !token || !token.includes('.')) return false;
  const [body, suppliedSig] = token.split('.', 2);
  if (!body || !suppliedSig || !safeEqual(await sign(body), suppliedSig)) return false;
  try {
    const payload = decodePayload(body);
    const username = String(payload.u || '');
    const expiresAt = Number(payload.exp || 0);
    return username === process.env.WEB_AUTH_USERNAME && expiresAt >= Math.floor(Date.now() / 1000);
  } catch {
    return false;
  }
}

export async function middleware(req: NextRequest) {
  const { pathname, search } = req.nextUrl;
  const authed = await verifyToken(req.cookies.get(COOKIE_NAME)?.value);

  if (pathname === '/login' || pathname.startsWith('/login/')) {
    if (authed) {
      const next = req.nextUrl.searchParams.get('next') || '/';
      return NextResponse.redirect(new URL(next.startsWith('/') ? next : '/', req.url));
    }
    return NextResponse.next();
  }

  if (isPublicPath(pathname)) return NextResponse.next();
  if (authed) return NextResponse.next();

  if (isBrowserHtml(pathname)) {
    const loginUrl = new URL('/login', req.url);
    loginUrl.searchParams.set('next', `${pathname}${search}`);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
}

export const config = {
  matcher: ['/((?!.*\\.).*)', '/api/:path*'],
};
