import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

/**
 * Sensitive-path / directory-listing hardening for the receipt-scanner app.
 *
 * Next.js standalone (`node server.js`, see Dockerfile) never generates
 * directory listings and only serves files that live under `public/` — so the
 * app itself is already safe by default. This module is defense-in-depth at
 * the application layer: even a misconfigured host, an accidentally
 * web-exposed build output, or a proxy that forwards everything still gets a
 * 404 for dotfiles, build artifacts, source files and other sensitive paths.
 *
 * RUNTIME CONSTRAINT: this file is imported by `src/middleware.ts` (Edge
 * runtime). It uses ONLY web standards — no Node.js APIs (no fs, no path, no
 * process, no net).
 *
 * BLOCKING POLICY (blocklist → 404; never a redirect, so no information leak;
 * no HTML page, just `{ "error": "Not found" }`):
 *
 *   1. Any path segment that starts with a dot — dotfiles / dot-directories at
 *      any depth: `/.env`, `/.env.local`, `/.git/config`, `/.dockerignore`,
 *      `/.npmrc`, `/.next/...`, `/.next-corrupt-*` dirs, `/api/.env`, ...
 *   2. Path traversal segments `.` / `..` (defense-in-depth; the WHATWG URL
 *      parser already normalizes most of these away before middleware runs).
 *   3. Directories that must never be web-reachable, at any depth:
 *      `node_modules`, `drizzle`, `scripts`. (`scripts/` is not web-facing in
 *      Next — only `public/` is served — but blocking it is harmless defense.
 *      `.next` and `.next-corrupt-*` are covered by rule 1.)
 *   4. Sensitive basenames at any depth: `docker-compose.yml` / `.yaml` /
 *      `.override.{yml,yaml}`, `Dockerfile`, `build_err.txt`, `package.json`,
 *      `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`,
 *      `npm-shrinkwrap.json`, plus prefix matches `tsconfig*`
 *      (`tsconfig.json`, `tsconfig.tsbuildinfo`, ...), `next.config.*`
 *      (`next.config.ts`, `next.config.mjs`, ...) and `drizzle.config.*`.
 *   5. Sensitive file extensions at any depth (case-insensitive):
 *      `.md`, `.pem`, `.key`, `.crt`, `.log`.
 *
 * LEGITIMATE `public/` ASSETS KEEP WORKING: demo images (`/demo/...`),
 * showcase images (`/showcase/*.png|jpg`), icons and `/app-icon.jpg` match
 * none of the patterns above. The middleware matcher additionally excludes
 * `_next/static`, `_next/image`, `favicon.ico`, `icon.png`, `apple-icon.png`
 * and `demo/` from running this code at all.
 */

/** True when the path contains any segment starting with a dot. */
const DOT_SEGMENT_RE = /(?:^|\/)\./;

/** Directory names that must never be web-reachable, at any depth. */
const BLOCKED_DIRS = new Set(["node_modules", "drizzle", "scripts"]);

/** Sensitive file extensions (case-insensitive), matched at any depth. */
const SENSITIVE_EXTENSION_RE = /\.(md|pem|key|crt|log)$/i;

/** Exact sensitive basenames (compared case-insensitively), at any depth. */
const BLOCKED_BASENAMES = new Set([
  "docker-compose.yml",
  "docker-compose.yaml",
  "docker-compose.override.yml",
  "docker-compose.override.yaml",
  "dockerfile",
  "build_err.txt",
  "package.json",
  "package-lock.json",
  "yarn.lock",
  "pnpm-lock.yaml",
  "npm-shrinkwrap.json",
]);

/** Basename prefixes identifying project/build config files, at any depth. */
const BLOCKED_BASENAME_PREFIXES = ["tsconfig", "next.config", "drizzle.config"];

/**
 * Pure policy predicate: is `pathname` a sensitive path that must be 404'd?
 *
 * Pure function of its input (a decoded URL pathname) — fully testable without
 * a request object. Returns `true` (block) for malformed input as well, so a
 * caller can never accidentally let a non-path string through.
 */
export function isSensitivePath(pathname: string): boolean {
  if (typeof pathname !== "string" || !pathname.startsWith("/")) {
    // Malformed input: block defensively rather than let it through.
    return true;
  }

  // Rule 1 — dotfiles / dot-directories at any depth.
  if (DOT_SEGMENT_RE.test(pathname)) return true;

  const segments = pathname.split("/").filter(Boolean);
  for (const segment of segments) {
    // Rule 2 — path traversal segments.
    if (segment === "." || segment === "..") return true;

    const lower = segment.toLowerCase();

    // Rule 3 — blocked directories.
    if (BLOCKED_DIRS.has(lower)) return true;

    // Rule 4 — sensitive basenames (exact match + prefix match).
    if (BLOCKED_BASENAMES.has(lower)) return true;
    for (const prefix of BLOCKED_BASENAME_PREFIXES) {
      if (lower.startsWith(prefix)) return true;
    }

    // Rule 5 — sensitive extensions.
    if (SENSITIVE_EXTENSION_RE.test(lower)) return true;
  }

  return false;
}

/** 404 response used for every blocked path (no redirect, no info leak). */
function notFound(): NextResponse {
  return NextResponse.json({ error: "Not found" }, { status: 404 });
}

/**
 * Middleware hook: returns a 404 `NextResponse` when the request targets a
 * sensitive path, otherwise `undefined` so request processing continues.
 *
 * Besides the decoded-pathname policy above, this rejects percent-encoded
 * traversal artefacts that the WHATWG URL parser may keep encoded in
 * `nextUrl.pathname`: `%2e` (encoded dot), `%5c` (encoded backslash), `%25`
 * (encoded percent — double-encoding) and raw backslashes. None of these ever
 * appear in a legitimate path of this app, so blocking them costs nothing and
 * closes encoded-bypass vectors like `/%2eenv` or `/%252eenv`.
 */
export function blockSensitivePath(request: NextRequest): NextResponse | undefined {
  const { pathname } = request.nextUrl;
  if (isSensitivePath(pathname)) return notFound();

  // Defense-in-depth against encoded path-name bypasses.
  if (/%2e|%5c|%25|\\/i.test(pathname)) return notFound();

  return undefined;
}
