import { NextResponse } from 'next/server';
import { withErrorHandler } from '@server/middleware/withErrorHandler';
import { withAuth, requireSection, AuthedRequest } from '@server/middleware/withAuth';
import { ValidationError } from '@server/errors';
import { updateKbEntry } from '@server/services/knowledge-base.service';
import { resolve } from 'dns/promises';
import { requirePlanFeature } from '@server/utils/features';

function stripHtmlToText(html: string): string {
  let text = html;
  text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');
  text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
  text = text.replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, '');
  text = text.replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, '');
  text = text.replace(/<header[^>]*>[\s\S]*?<\/header>/gi, '');
  text = text.replace(/<[^>]+>/g, ' ');
  text = text.replace(/&nbsp;/g, ' ');
  text = text.replace(/&amp;/g, '&');
  text = text.replace(/&lt;/g, '<');
  text = text.replace(/&gt;/g, '>');
  text = text.replace(/&quot;/g, '"');
  text = text.replace(/&#39;/g, "'");
  text = text.replace(/\s+/g, ' ');
  return text.trim();
}

function isPrivateIp(ip: string): boolean {
  if (ip === '127.0.0.1' || ip === '::1' || ip === 'localhost') return true;
  const parts = ip.split('.').map(Number);
  if (parts.length === 4) {
    if (parts[0] === 10) return true;
    if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
    if (parts[0] === 192 && parts[1] === 168) return true;
    if (parts[0] === 169 && parts[1] === 254) return true;
    if (parts[0] === 0) return true;
    if (parts[0] === 127) return true;
  }
  if (ip.startsWith('fc') || ip.startsWith('fd') || ip.startsWith('fe80')) return true;
  return false;
}

async function validateUrl(url: string): Promise<void> {
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error('Invalid URL format');
  }

  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new Error('Only http and https URLs are allowed');
  }

  const hostname = parsed.hostname;
  if (isPrivateIp(hostname)) {
    throw new Error('URLs pointing to internal/private networks are not allowed');
  }

  if (hostname === 'metadata.google.internal' || hostname === '169.254.169.254') {
    throw new Error('Cloud metadata endpoints are not allowed');
  }

  try {
    const addresses = await resolve(hostname);
    for (const addr of addresses) {
      if (isPrivateIp(addr)) {
        throw new Error('URL resolves to a private/internal IP address');
      }
    }
  } catch (err: any) {
    if (err.code === 'ENOTFOUND') {
      throw new Error('Could not resolve hostname');
    }
    throw err;
  }
}

const MAX_REDIRECTS = 5;

async function safeFetch(url: string): Promise<Response> {
  let currentUrl = url;
  for (let i = 0; i <= MAX_REDIRECTS; i++) {
    await validateUrl(currentUrl);

    const response = await fetch(currentUrl, {
      headers: {
        'User-Agent': 'Mozilla/5.0 (compatible; RestroAgent/1.0)',
        Accept: 'text/html,application/xhtml+xml,text/plain,*/*',
      },
      redirect: 'manual',
      signal: AbortSignal.timeout(15000),
    });

    if (response.status >= 300 && response.status < 400) {
      const location = response.headers.get('location');
      if (!location) throw new Error('Redirect without Location header');
      currentUrl = new URL(location, currentUrl).toString();
      continue;
    }

    return response;
  }

  throw new Error('Too many redirects');
}

export const POST = withErrorHandler(
  withAuth(async (req: AuthedRequest) => {
    const { restaurantId } = req.session;
    await requireSection(req, 'knowledge_base', 'update');
    await requirePlanFeature(restaurantId!, 'knowledge_base');
    let body: Awaited<ReturnType<typeof req.json>>;
    try {
      body = await req.json();
    } catch {
      throw new ValidationError('Request body must be valid JSON');
    }
    const { id, url } = body as { id: string; url: string };

    if (!id || !url) {
      return NextResponse.json({ error: 'Missing id or url' }, { status: 400 });
    }

    try {
      await validateUrl(url);
    } catch (err: any) {
      await updateKbEntry(id, restaurantId!, { status: 'error' }).catch(() => {});
      return NextResponse.json({ error: err.message }, { status: 400 });
    }

    try {
      const response = await safeFetch(url);

      if (!response.ok) {
        await updateKbEntry(id, restaurantId!, { status: 'error' });
        return NextResponse.json(
          { error: `Failed to fetch URL: HTTP ${response.status}` },
          { status: 400 }
        );
      }

      const contentType = response.headers.get('content-type') || '';
      const rawText = await response.text();
      let content: string;

      if (contentType.includes('text/html') || contentType.includes('xhtml')) {
        content = stripHtmlToText(rawText);
      } else {
        content = rawText;
      }

      if (!content.trim()) {
        await updateKbEntry(id, restaurantId!, { status: 'error' });
        return NextResponse.json(
          { error: 'No readable content found at URL' },
          { status: 400 }
        );
      }

      const maxLen = 50000;
      if (content.length > maxLen) {
        content = content.slice(0, maxLen);
      }

      const entry = await updateKbEntry(id, restaurantId!, {
        content,
        status: 'active',
      });

      return NextResponse.json({ entry, contentLength: content.length });
    } catch (err: any) {
      await updateKbEntry(id, restaurantId!, { status: 'error' }).catch(() => {});
      return NextResponse.json(
        { error: `Failed to crawl URL: ${err.message}` },
        { status: 400 }
      );
    }
  })
);
