const express = require('express');
const rateLimit = require('express-rate-limit');
const path = require('path');
const { validateUrl } = require('../lib/ssrf');

const router = express.Router();

// History (per session)
router.get('/api/history', (req, res) => {
  res.json(req.session.httpHistory || []);
});

router.post('/api/history/add', express.json(), (req, res) => {
  if (!req.session.httpHistory) req.session.httpHistory = [];
  req.session.httpHistory.unshift(req.body);
  if (req.session.httpHistory.length > 30) req.session.httpHistory.length = 30;
  res.json({ ok: true });
});

router.delete('/api/history', (req, res) => {
  req.session.httpHistory = [];
  res.json({ ok: true });
});

// Proxy
const proxyLimiter = rateLimit({
  keyGenerator: (req) => (req.session && req.session.user && req.session.user.id) ? "user_" + req.session.user.id : req.ip,
  windowMs: 60 * 1000,
  max: parseInt(process.env.PROXY_RATE_LIMIT_MAX) || 60,
  message: { error: 'Слишком много запросов. Попробуйте через минуту.' },
});

router.post('/api/proxy', proxyLimiter, express.json({ limit: '10mb' }), async (req, res) => {
  const { url, method = 'GET', headers = {}, body, timeout = 30000 } = req.body || {};

  const check = await validateUrl(url);
  if (!check.safe) return res.status(400).json({ error: check.error });

  const startTime = Date.now();

  try {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), Math.min(Number(timeout) || 30000, 60000));

    let curMethod = method.toUpperCase();
    const fetchOptions = {
      method: curMethod,
      headers: Object.fromEntries(
        Object.entries(headers).filter(([k]) => !['host', 'connection', 'transfer-encoding'].includes(k.toLowerCase()))
      ),
      signal: controller.signal,
      redirect: 'manual',
    };

    if (body != null && !['GET', 'HEAD'].includes(curMethod)) {
      fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
    }

    // Follow redirects manually, re-validating each hop against SSRF rules.
    const MAX_REDIRECTS = 5;
    let currentUrl = check.url;
    let response;
    for (let i = 0; ; i++) {
      response = await fetch(currentUrl, fetchOptions);
      if (![301, 302, 303, 307, 308].includes(response.status)) break;
      if (i >= MAX_REDIRECTS) { clearTimeout(timer); return res.status(400).json({ error: 'Слишком много редиректов' }); }
      const loc = response.headers.get('location');
      if (!loc) break;
      let nextUrl;
      try { nextUrl = new URL(loc, currentUrl).href; } catch { clearTimeout(timer); return res.status(400).json({ error: 'Некорректный редирект' }); }
      const rcheck = await validateUrl(nextUrl);
      if (!rcheck.safe) { clearTimeout(timer); return res.status(400).json({ error: 'Редирект на запрещённый адрес: ' + rcheck.error }); }
      currentUrl = rcheck.url;
      // 303 (and 301/302 for non-GET/HEAD) → switch to GET without body, per HTTP semantics
      if (response.status === 303 || (response.status < 307 && !['GET', 'HEAD'].includes(curMethod))) {
        curMethod = 'GET';
        fetchOptions.method = 'GET';
        delete fetchOptions.body;
      }
    }
    clearTimeout(timer);

    const elapsed = Date.now() - startTime;
    const responseHeaders = {};
    response.headers.forEach((value, key) => { responseHeaders[key] = value; });

    const MAX_SIZE = 10 * 1024 * 1024;
    const reader = response.body?.getReader();
    const chunks = [];
    let totalSize = 0;
    let truncated = false;

    if (reader) {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        totalSize += value.length;
        if (totalSize > MAX_SIZE) { truncated = true; break; }
        chunks.push(value);
      }
    }

    const buffer = Buffer.concat(chunks.map(c => Buffer.from(c)));
    const responseBody = buffer.toString('utf-8');

    res.json({
      status: response.status, statusText: response.statusText,
      headers: responseHeaders, body: responseBody,
      time: elapsed, size: totalSize, truncated, url: response.url,
    });
  } catch (err) {
    const elapsed = Date.now() - startTime;
    if (err.name === 'AbortError') return res.status(408).json({ error: 'Таймаут запроса', time: elapsed });
    res.status(500).json({ error: err.message, time: elapsed });
  }
});

// Page
router.get('/httpclient', (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'public', 'httpclient.html'));
});

module.exports = router;
