/**
 * Locale-prefix middleware.
 *
 * Intercepts requests matching `/[locale]/deals/[slug]` and:
 *   1. Validates the locale against the active languages registry.
 *   2. Looks up the deal translation by (locale, slug).
 *   3. On hit  → sets locals.dealHit + locals.locale, continues.
 *   4. On miss → checks deal_slug_redirects; if found, issues 301.
 *   5. Unknown locale or slug not a deal → falls through to Astro router (browse routes).
 *
 * For non-deal locale-prefixed routes (`/[locale]/...` that are NOT deals),
 * this middleware just validates locale and sets locals.locale, then continues.
 *
 * For requests that don't match the locale-prefix pattern at all, pass through
 * immediately so the existing locale detection middleware handles them.
 */

import { defineMiddleware } from 'astro:middleware';
import { getActiveLanguagesCached } from '@/server/i18n/languages/cache.js';
import {
  findDealByLocaleSlug,
  findRedirectByLocaleSlug,
} from '@/server/catalog/public/translations';
import { captureCaught } from '@/server/observability/capture.server';

// Pattern: /[locale]/deals/[slug]  — locale = exactly 2 lowercase letters (ISO 639-1)
const DEAL_PATH_RE = /^\/([a-z]{2})\/deals\/([^/?#]+)/;
// Pattern: any /[locale]/... route — exactly 2 lowercase letters to avoid matching
// /api/, /admin/, /vendor/ etc. which are 3+ chars and must never be intercepted.
const LOCALE_PATH_RE = /^\/([a-z]{2})\//;
// Pattern: bare /deals/[slug] — Hebrew canonical deal URLs (no locale prefix).
// Only matches single-segment paths to avoid intercepting browse routes like
// /deals/<cat>/<tag>. Known browse-only slugs (coupon, group, all, page-N) fall
// through on DB miss so parseDealsPath handles them as before.
const HE_DEAL_PATH_RE = /^\/deals\/([^/?#]+)$/;

// Hardcoded fallback — mirrors locale.ts SUPPORTED_LOCALES.
// Used when the languages DB table is empty or unavailable (e.g. test env without seed).
const FALLBACK_LOCALES = new Set(['he', 'en']);

// Two-letter path segments that look like locales but are real routes.
// Without this exclusion, LOCALE_PATH_RE matches /qr/<key> and returns 404
// before Astro's router can handle it.
const NON_LOCALE_SEGMENTS = new Set(['qr']);

export const localePrefixMiddleware = defineMiddleware(async (context, next) => {
  const pathname = new URL(context.request.url).pathname;

  // ── Hebrew canonical deal URLs: /deals/<slug> (no locale prefix) ─────────────
  // Must be checked BEFORE the locale-prefix fast-path since these URLs do not
  // carry a locale prefix. Only attempt the DB lookup for single-segment paths;
  // multi-segment paths (/deals/<cat>/<tag>) route to the browse page directly.
  const heSlugMatch = HE_DEAL_PATH_RE.exec(pathname);
  if (heSlugMatch) {
    const heSlugRaw = heSlugMatch[1]!;
    const heSlug = decodeURIComponent(heSlugRaw);
    try {
      const hit = await findDealByLocaleSlug('he', heSlug);
      if (hit) {
        context.locals.dealHit = hit;
        context.locals.locale = 'he';
        // Rewrite to /en/deals/<slug> so Astro can route it via
        // pages/[locale]/deals/[...path].astro — locale is already pinned to 'he'.
        const requestUrl = new URL(context.request.url);
        const rewriteUrl = new URL(`/en/deals/${heSlug}`, requestUrl);
        rewriteUrl.search = requestUrl.search;
        context.locals.localeFixed = true;
        return context.rewrite(rewriteUrl);
      }
      // Check redirect table
      const currentSlug = await findRedirectByLocaleSlug('he', heSlug);
      if (currentSlug) {
        return context.redirect(`/deals/${currentSlug}`, 301);
      }
    } catch (err) {
      captureCaught(err, { scope: 'middleware.locale-prefix.he-deal', severity: 'error' });
      console.error(JSON.stringify({ msg: 'locale_prefix_he_deal_db_error', err: String(err) }));
      return new Response('Not Found', { status: 404 });
    }
    // Not a deal slug — fall through to browse route (parseDealsPath handles it)
    return next();
  }

  // Fast-path: not a locale-prefixed route at all
  const localeMatch = LOCALE_PATH_RE.exec(pathname);
  if (!localeMatch) return next();

  const candidateLocale = localeMatch[1]!;

  // Pass through known non-locale 2-letter route prefixes (e.g. /qr/, /r2/).
  if (NON_LOCALE_SEGMENTS.has(candidateLocale)) return next();

  // Validate locale against active languages registry
  let activeLanguages: Awaited<ReturnType<typeof getActiveLanguagesCached>> = [];
  try {
    activeLanguages = await getActiveLanguagesCached();
  } catch (err) {
    captureCaught(err, { scope: 'middleware.locale-prefix.languages-cache', severity: 'warning' });
    // DB unavailable — fall through to FALLBACK_LOCALES
  }
  const langRow = activeLanguages.find((l) => l.code === candidateLocale);

  if (!langRow) {
    // DB may be empty (e.g. test env without seed) — fall back to hardcoded supported locales.
    // If not in fallback set either, return 404.
    if (!FALLBACK_LOCALES.has(candidateLocale)) {
      return new Response('Not Found', { status: 404 });
    }
  }

  context.locals.localeValidated = true;

  // Locale is valid: override locals.locale (takes precedence over cookie/Accept-Language).
  // Skip if already pinned by a prior rewrite pass (localeFixed prevents the
  // second middleware run — triggered by context.rewrite — from clobbering the
  // original locale that was set before the rewrite).
  if (!context.locals.localeFixed) {
    context.locals.locale = candidateLocale as App.Locals['locale'];
  }

  // Check if this is a deal detail route
  const dealMatch = DEAL_PATH_RE.exec(pathname);
  if (!dealMatch) {
    // Valid locale prefix, non-deal route.
    //
    // Astro i18n is configured with `prefixDefaultLocale: false`, meaning the
    // default locale (`he`) has NO URL prefix in Astro's router — its pages
    // live at root paths. Non-default locales (e.g. `en`) ARE prefixed and
    // route correctly through `pages/[locale]/...`.
    //
    // When a request arrives at `/he/[path]`, Astro's router cannot match it
    // to any page (there is no `pages/he/` directory and the default locale
    // has no prefix). We fix this by internally rewriting the request URL from
    // `/he/[rest]` to `/en/[rest]` — a path Astro CAN route via
    // `pages/[locale]/...`. The rewrite is transparent: `locals.locale` has
    // already been set to `'he'` above, so every page/layout that reads
    // `Astro.locals.locale` will render Hebrew content correctly.
    if (candidateLocale === 'he') {
      const rest = pathname.slice('/he'.length); // e.g. '/categories/bakery'
      const requestUrl = new URL(context.request.url);
      const rewriteUrl = new URL(`/en${rest}`, requestUrl);
      rewriteUrl.search = requestUrl.search;
      // Pin locale before rewrite so the second middleware pass (re-triggered
      // by context.rewrite with /en/... URL) does not overwrite 'he' with 'en'.
      context.locals.localeFixed = true;
      return context.rewrite(rewriteUrl);
    }

    return next();
  }

  const slug = decodeURIComponent(dealMatch[2]!);

  // Look up translation — wrap in try/catch so DB errors return 404, not 500
  try {
    const hit = await findDealByLocaleSlug(candidateLocale, slug);

    if (hit) {
      context.locals.dealHit = hit;
      return next();
    }

    // Check redirect table
    const currentSlug = await findRedirectByLocaleSlug(candidateLocale, slug);
    if (currentSlug) {
      return context.redirect(`/${candidateLocale}/deals/${currentSlug}`, 301);
    }
  } catch (err) {
    captureCaught(err, { scope: 'middleware.locale-prefix', severity: 'error' });
    console.error(JSON.stringify({ msg: 'locale_prefix_db_error', err: String(err) }));
    return new Response('Not Found', { status: 404 });
  }

  // Slug not a deal translation — let Astro route it (browse routes, etc.).
  //
  // For the default locale (`he`), Astro has no `pages/he/` directory
  // (prefixDefaultLocale: false), so falling through with /he/deals/... URL
  // would produce a 503. Apply the same rewrite used by the non-deal branch
  // above: /he/deals/<rest> → /en/deals/<rest> with locale pinned to 'he'.
  if (candidateLocale === 'he') {
    const requestUrl = new URL(context.request.url);
    const rest = pathname.slice('/he'.length); // e.g. '/deals/restaurant'
    const rewriteUrl = new URL(`/en${rest}`, requestUrl);
    rewriteUrl.search = requestUrl.search;
    context.locals.localeFixed = true;
    return context.rewrite(rewriteUrl);
  }

  return next();
});
