import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import * as cheerio from 'cheerio';
import { withAuth } from '@/core/auth';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

/**
 * Scrapt eine Webseite und extrahiert Briefing-relevante Metadata:
 *  - Title, Description (Meta + OpenGraph)
 *  - Headlines (H1-H3)
 *  - Body-Text (~erste 3000 Zeichen, sichtbarer Text)
 *  - Bilder mit absoluten URLs
 *
 * Sicherheit:
 *  - Nur http(s)-URLs
 *  - SSRF-Block: keine privaten IP-Ranges (localhost, 10.x, 172.16-31.x, 192.168.x, 169.254.x)
 *  - Hard-Timeout 8 Sek
 *  - Max 5 MB HTML-Größe (große Pages sind selten Landing-Pages)
 *  - Custom User-Agent damit Server uns als Crawler erkennen können (Bot-Etikette)
 */
const RequestSchema = z.object({
  url: z.string().url(),
});

const SCRAPE_TIMEOUT_MS = 8000;
const MAX_HTML_BYTES = 5 * 1024 * 1024;
const USER_AGENT = 'Mozilla/5.0 (compatible; MailForgeBot/1.0; +https://mailforge.local)';

/**
 * SSRF-Guard: blockiert URLs auf private/loopback Adressen.
 * Best-effort — DNS-Resolution beim Fetch macht dann den Real-Check.
 * Hier filtern wir nur die offensichtlichsten Fälle vorab um schnelle Rejects zu liefern.
 */
function isUrlSafe(rawUrl: string): { safe: boolean; reason?: string } {
  let u: URL;
  try {
    u = new URL(rawUrl);
  } catch {
    return { safe: false, reason: 'Ungültige URL' };
  }
  if (u.protocol !== 'http:' && u.protocol !== 'https:') {
    return { safe: false, reason: 'Nur http(s) erlaubt' };
  }
  const host = u.hostname.toLowerCase();
  // Loopback + Localhost-Varianten
  if (host === 'localhost' || host.endsWith('.localhost')) return { safe: false, reason: 'Localhost gesperrt' };
  if (host === '127.0.0.1' || host.startsWith('127.')) return { safe: false, reason: 'Loopback gesperrt' };
  if (host === '::1' || host === '0.0.0.0') return { safe: false, reason: 'Loopback gesperrt' };
  // AWS/Cloud-Metadata-Endpoints
  if (host === '169.254.169.254' || host.startsWith('169.254.')) return { safe: false, reason: 'Link-Local gesperrt' };
  // RFC1918 private ranges
  if (host.startsWith('10.')) return { safe: false, reason: 'Private IP gesperrt' };
  if (host.startsWith('192.168.')) return { safe: false, reason: 'Private IP gesperrt' };
  // 172.16.0.0 - 172.31.255.255
  const m172 = host.match(/^172\.(\d+)\./);
  if (m172) {
    const second = parseInt(m172[1], 10);
    if (second >= 16 && second <= 31) return { safe: false, reason: 'Private IP gesperrt' };
  }
  return { safe: true };
}

export interface ScrapedImage {
  url: string;
  alt: string;
  width?: number;
  height?: number;
}

export interface ScrapeResult {
  url: string;
  title: string;
  description: string;
  ogImage: string | null;
  headlines: string[]; // h1-h3 in Reihenfolge
  bodyText: string;
  images: ScrapedImage[];
  /** Aggregat-Text für AI-Suggest — Title + Description + Headlines + erste Body-Zeilen. */
  briefText: string;
}

export const POST = withAuth(async (req: NextRequest, _ctx) => {
  try {
    const body = await req.json();
    const parsed = RequestSchema.safeParse(body);
    if (!parsed.success) {
      return NextResponse.json({
        error: 'Invalid input',
        issues: parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`),
      }, { status: 400 });
    }

    const { url } = parsed.data;
    const safety = isUrlSafe(url);
    if (!safety.safe) {
      return NextResponse.json({ error: `URL gesperrt: ${safety.reason}` }, { status: 400 });
    }

    // Fetch mit Timeout via AbortController
    const controller = new AbortController();
    const timeoutHandle = setTimeout(() => controller.abort(), SCRAPE_TIMEOUT_MS);
    let html: string;
    try {
      const res = await fetch(url, {
        method: 'GET',
        headers: { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml' },
        signal: controller.signal,
        redirect: 'follow',
      });
      if (!res.ok) {
        return NextResponse.json({ error: `Server antwortete mit ${res.status} ${res.statusText}` }, { status: 502 });
      }
      const contentType = res.headers.get('content-type') ?? '';
      if (!contentType.includes('text/html') && !contentType.includes('application/xhtml')) {
        return NextResponse.json({ error: `Kein HTML — Content-Type: ${contentType}` }, { status: 415 });
      }
      // Read als Text — fetch hat keinen Streaming-Cap, aber wir arbeiten mit text() weiter und truncaten
      const text = await res.text();
      html = text.length > MAX_HTML_BYTES ? text.slice(0, MAX_HTML_BYTES) : text;
    } catch (e) {
      const msg = e instanceof Error ? e.message : 'Network error';
      const isTimeout = e instanceof Error && e.name === 'AbortError';
      return NextResponse.json(
        { error: isTimeout ? `Timeout nach ${SCRAPE_TIMEOUT_MS}ms` : msg },
        { status: isTimeout ? 504 : 502 },
      );
    } finally {
      clearTimeout(timeoutHandle);
    }

    const result = extractFromHtml(html, url);
    return NextResponse.json({ ok: true, scrape: result });
  } catch (err) {
    console.error('[/api/scrape-url] error:', err);
    return NextResponse.json(
      { error: err instanceof Error ? err.message : 'Internal error' },
      { status: 500 },
    );
  }
});

/**
 * Extrahiert strukturierte Daten aus dem HTML mit cheerio. Bilder werden zu absoluten URLs aufgelöst.
 * Filtert sehr kleine / Tracking-Pixel-artige Bilder raus (≤ 50px wenn explizit in Attributen) und
 * Data-URIs, weil die für Email-Hero-Use nicht brauchbar sind.
 */
function extractFromHtml(html: string, sourceUrl: string): ScrapeResult {
  const $ = cheerio.load(html);
  const baseUrl = new URL(sourceUrl);

  const title = (
    $('meta[property="og:title"]').attr('content')
    || $('meta[name="twitter:title"]').attr('content')
    || $('title').first().text()
    || ''
  ).trim();

  const description = (
    $('meta[property="og:description"]').attr('content')
    || $('meta[name="description"]').attr('content')
    || $('meta[name="twitter:description"]').attr('content')
    || ''
  ).trim();

  const ogImageRaw = $('meta[property="og:image"]').attr('content') ?? null;
  const ogImage = ogImageRaw ? toAbsoluteUrl(ogImageRaw, baseUrl) : null;

  const headlines: string[] = [];
  $('h1, h2, h3').each((_, el) => {
    const txt = $(el).text().trim().replace(/\s+/g, ' ');
    if (txt && txt.length > 3 && txt.length < 200) headlines.push(txt);
  });
  const uniqueHeadlines = Array.from(new Set(headlines)).slice(0, 15);

  // Body-Text: alle <p>, <li>, <span> mit Inhalt — kein <script>, <style>
  $('script, style, noscript, nav, footer, header, aside').remove();
  const bodyTextRaw = $('body').text().replace(/\s+/g, ' ').trim();
  const bodyText = bodyTextRaw.slice(0, 3000);

  // Bilder sammeln — absoluteUrl, alt, optional w/h
  const seenUrls = new Set<string>();
  const images: ScrapedImage[] = [];

  // OG-Image als ersten Eintrag falls vorhanden (typisch das Best-Pick-Bild der Seite)
  if (ogImage && !seenUrls.has(ogImage)) {
    seenUrls.add(ogImage);
    images.push({ url: ogImage, alt: title || 'OpenGraph Image' });
  }

  $('img').each((_, el) => {
    const src = $(el).attr('src') || $(el).attr('data-src') || $(el).attr('data-lazy-src');
    if (!src) return;
    if (src.startsWith('data:')) return; // Data-URIs überspringen
    const absUrl = toAbsoluteUrl(src, baseUrl);
    if (!absUrl) return;
    if (seenUrls.has(absUrl)) return;

    const alt = ($(el).attr('alt') || '').trim();
    const widthAttr = parseInt($(el).attr('width') || '', 10);
    const heightAttr = parseInt($(el).attr('height') || '', 10);

    // Heuristik: explizit deklarierte sehr kleine Bilder filtern (Tracking-Pixel, Icons)
    if (!isNaN(widthAttr) && widthAttr > 0 && widthAttr <= 50) return;
    if (!isNaN(heightAttr) && heightAttr > 0 && heightAttr <= 50) return;

    // Filter offensichtliche Icons/Logos via Pfad
    const lower = absUrl.toLowerCase();
    if (/\b(favicon|sprite|tracking-pixel)\b/.test(lower)) return;

    seenUrls.add(absUrl);
    images.push({
      url: absUrl,
      alt: alt || 'Bild ohne Alt-Text',
      width: !isNaN(widthAttr) && widthAttr > 0 ? widthAttr : undefined,
      height: !isNaN(heightAttr) && heightAttr > 0 ? heightAttr : undefined,
    });
  });

  // Aggregierter briefText für KI-Suggest: prägnant, nur die starken Signale
  const briefText = [
    title && `# ${title}`,
    description && description,
    uniqueHeadlines.length > 0 && '\n## Headlines\n' + uniqueHeadlines.map((h) => `- ${h}`).join('\n'),
    bodyText && '\n## Inhalt\n' + bodyText.slice(0, 1500),
  ].filter(Boolean).join('\n\n');

  return {
    url: sourceUrl,
    title,
    description,
    ogImage,
    headlines: uniqueHeadlines,
    bodyText,
    images: images.slice(0, 30), // Max 30 Bilder — verhindert UI-Overload
    briefText,
  };
}

/** Resolved relative URLs gegen die Base-URL der Quellseite. */
function toAbsoluteUrl(src: string, base: URL): string | null {
  try {
    return new URL(src, base).toString();
  } catch {
    return null;
  }
}
