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

// Toggle by setting NEXT_PUBLIC_MAINTENANCE_MODE=1 (or "true") and restarting the server.
const MAINTENANCE_MODE =
  process.env.NEXT_PUBLIC_MAINTENANCE_MODE === '1' ||
  process.env.NEXT_PUBLIC_MAINTENANCE_MODE === 'true';

// Comma-separated list of IPs allowed through during maintenance (e.g. internal QA).
const MAINTENANCE_ALLOWLIST = (process.env.MAINTENANCE_ALLOWLIST || '')
  .split(',')
  .map((s) => s.trim())
  .filter(Boolean);

const CACHE_TTL_MS = 5 * 60 * 1000;

// ─── Domain path prefixes, derived from the sales-channel DOMAINS in admin ───
// A domain URL like "http://localhost:3000/de-DEs" contributes the routable
// prefix "/de-DEs". The prefix is whatever the admin typed — it is NOT assumed
// to equal the locale code, so custom prefixes ("/de", "/germany") work exactly
// like Shopware's own storefront. The list comes from this app's own
// /api/languages route (which reads the domains via the Admin API), cached.
interface DomainPrefix {
  prefix: string; // e.g. "/de-DEs" (never empty; the default domain has no prefix)
  languageId: string;
  locale: string; // locale code, e.g. "de-DE" — used for snippets/i18n
}

let prefixCache: DomainPrefix[] | null = null;
let prefixCacheFetchedAt = 0;

async function getDomainPrefixes(origin: string): Promise<DomainPrefix[]> {
  const now = Date.now();
  if (prefixCache && now - prefixCacheFetchedAt < CACHE_TTL_MS) {
    return prefixCache;
  }
  try {
    const res = await fetch(`${origin}/api/languages`, {
      headers: { 'x-mw-internal': '1' },
      cache: 'no-store',
    });
    if (!res.ok) return prefixCache || [];
    const data: { languages?: Array<{ languageId: string; localeCode: string; url: string }> } =
      await res.json();
    const list: DomainPrefix[] = [];
    for (const l of data.languages || []) {
      let prefix = '';
      try {
        prefix = new URL(l.url).pathname.replace(/\/+$/, ''); // "/de-DEs" or "" for the root domain
      } catch {
        prefix = '';
      }
      if (prefix) list.push({ prefix, languageId: l.languageId, locale: l.localeCode });
    }
    // Longest prefix first so nested prefixes ("/de-DE" vs "/de") match the
    // most specific domain rather than a shorter sibling.
    list.sort((a, b) => b.prefix.length - a.prefix.length);
    prefixCache = list;
    prefixCacheFetchedAt = now;
    return list;
  } catch {
    return prefixCache || [];
  }
}

function isAssetPath(pathname: string): boolean {
  return (
    pathname.startsWith('/_next') ||
    pathname.startsWith('/api') ||
    pathname.startsWith('/favicon') ||
    pathname.startsWith('/public') ||
    /\.(png|jpg|jpeg|gif|svg|webp|ico|css|js|map|woff2?)$/i.test(pathname)
  );
}

export async function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;

  // Always allow Next internals, API, and static assets to fall through.
  if (isAssetPath(pathname)) {
    return NextResponse.next();
  }

  // ─── Maintenance ─────────────────────────────────────────────────────────
  if (MAINTENANCE_MODE && !pathname.startsWith('/maintenance')) {
    const ip = req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '';
    const onAllowlist = ip && MAINTENANCE_ALLOWLIST.includes(ip);
    if (!onAllowlist) {
      const url = req.nextUrl.clone();
      url.pathname = '/maintenance';
      url.search = '';
      return NextResponse.rewrite(url);
    }
  }

  // ─── Domain prefix handling ──────────────────────────────────────────────
  // Map paths like "/de-DEs/Bekleidung" → internal "/Bekleidung" and forward
  // the matched domain's language so the first SSR call renders in the right
  // language. The set of recognised prefixes is whatever the admin configured —
  // not a fixed locale pattern — so any custom prefix routes correctly.
  const prefixes = await getDomainPrefixes(req.nextUrl.origin);
  const matched = prefixes.find(
    (p) => pathname === p.prefix || pathname.startsWith(`${p.prefix}/`)
  );

  if (matched) {
    const stripped = pathname.slice(matched.prefix.length) || '/';
    const url = req.nextUrl.clone();
    url.pathname = stripped;

    const forwardedHeaders = new Headers(req.headers);
    forwardedHeaders.set('x-sw-locale', matched.locale);
    forwardedHeaders.set('x-sw-language-id', matched.languageId);
    // The actual URL prefix, so the SSR render can build localized hrefs that
    // point back at this domain (see app/layout.tsx → SalesChannelProvider).
    forwardedHeaders.set('x-sw-prefix', matched.prefix);

    const res = NextResponse.rewrite(url, {
      request: { headers: forwardedHeaders },
    });
    const cookieOpts = { path: '/', sameSite: 'lax' as const, maxAge: 60 * 60 * 24 * 30 };
    res.cookies.set('sw-locale', matched.locale, cookieOpts);
    res.cookies.set('sw-prefix', matched.prefix, cookieOpts);
    return res;
  }

  // No prefix → default domain. Clear any stale locale/prefix cookies so the
  // default language wins.
  const res = NextResponse.next();
  if (req.cookies.get('sw-locale')) {
    res.cookies.set('sw-locale', '', { path: '/', maxAge: 0 });
  }
  if (req.cookies.get('sw-prefix')) {
    res.cookies.set('sw-prefix', '', { path: '/', maxAge: 0 });
  }
  return res;
}

export const config = {
  // Skip all internal Next.js paths and any URL with a file extension. The
  // file-extension exclusion is critical: in dev mode Next.js sometimes
  // routes URLs like /_next/static/css/app/layout.css through the catch-all
  // [...slug] route if middleware accidentally short-circuits them, which
  // breaks the whole frontend (no CSS loads).
  matcher: ['/((?!api|_next/|favicon.ico|.*\\..*).*)'],
};
