const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { connect } = require('puppeteer-real-browser');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());
app.use(bodyParser.json());

let globalBrowser = null;
let activeRequests = 0;

// Pre-launch the global browser instance for faster execution of individual requests
async function initBrowser() {
  try {
    console.log("[SERVER] Launching global browser instance...");
    const { browser } = await connect({
      headless: false,
      turnstile: true,
      connectOption: {
        defaultViewport: null
      },
      disableXvfb: false
    });
    
    globalBrowser = browser;
    console.log("[SERVER] Global browser instance ready.");

    globalBrowser.on('disconnected', async () => {
      console.log("[SERVER] Browser disconnected! Re-initializing...");
      await new Promise(resolve => setTimeout(resolve, 3000));
      await initBrowser();
    });
  } catch (error) {
    console.error("[SERVER] Failed to launch browser:", error.message);
    await new Promise(resolve => setTimeout(resolve, 3000));
    await initBrowser();
  }
}

// Inline Turnstile minimal template HTML to prevent file resolution errors
const turnstileHtmlTemplate = (siteKey) => `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Solving Captcha...</title>
</head>
<body>
    <div class="turnstile" style="margin: 50px auto; display: table;"></div>
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
    <script>
        window.onloadTurnstileCallback = function () {
            turnstile.render('.turnstile', {
                sitekey: '${siteKey}',
                callback: function (token) {
                    var c = document.createElement('input');
                    c.type = 'hidden';
                    c.name = 'cf-response';
                    c.value = token;
                    document.body.appendChild(c);
                },
            });
        };
    </script>
</body>
</html>
`;

// Endpoint: POST /bypass
app.post('/bypass', async (req, res) => {
  const { url, mode = 'waf-session', siteKey, proxy } = req.body;

  if (!url) {
    return res.status(400).json({ code: 400, message: "Missing 'url' parameter" });
  }

  if (!globalBrowser) {
    return res.status(503).json({ code: 503, message: "Browser is not ready yet. Please try again." });
  }

  activeRequests++;
  console.log(`[SERVER] [Active: ${activeRequests}] Processing request: Mode=${mode}, URL=${url}`);

  let context = null;
  let isResolved = false;

  // Set timeout safety mechanism
  const timeoutMs = 60000;
  const timeoutId = setTimeout(async () => {
    if (!isResolved) {
      console.log(`[SERVER] Request timed out for: ${url}`);
      if (context) await context.close().catch(() => {});
      activeRequests--;
      res.status(504).json({ code: 504, message: "Bypass Timeout Error" });
    }
  }, timeoutMs);

  try {
    // Create an isolated browser context for this request
    context = await globalBrowser.createBrowserContext({
      proxyServer: proxy ? `http://${proxy.host}:${proxy.port}` : undefined
    });

    const page = await context.newPage();

    if (proxy && proxy.username && proxy.password) {
      await page.authenticate({
        username: proxy.username,
        password: proxy.password
      });
    }

    if (mode === 'turnstile-min') {
      // Serve a local fake page containing the Turnstile widget directly
      if (!siteKey) {
        clearTimeout(timeoutId);
        activeRequests--;
        await context.close();
        return res.status(400).json({ code: 400, message: "Missing 'siteKey' parameter for 'turnstile-min' mode." });
      }

      await page.setRequestInterception(true);
      page.on('request', async (request) => {
        if ([url, url + '/'].includes(request.url()) && request.resourceType() === 'document') {
          await request.respond({
            status: 200,
            contentType: 'text/html',
            body: turnstileHtmlTemplate(siteKey)
          });
        } else {
          await request.continue();
        }
      });

      await page.goto(url, { waitUntil: 'domcontentloaded' });
      await page.waitForSelector('[name="cf-response"]', { timeout: 45000 });

      const token = await page.evaluate(() => {
        const input = document.querySelector('[name="cf-response"]');
        return input ? input.value : null;
      });

      isResolved = true;
      clearTimeout(timeoutId);
      await context.close();
      activeRequests--;

      if (!token) return res.status(500).json({ code: 500, message: "Failed to resolve Turnstile token" });
      return res.json({ code: 200, token });

    } else if (mode === 'turnstile-max') {
      // Go to the actual page, wait for the inline Turnstile to be solved, and get token
      await page.evaluateOnNewDocument(() => {
        let token = null;
        async function waitForToken() {
          while (!token) {
            try {
              token = window.turnstile.getResponse();
            } catch (e) {}
            await new Promise((resolve) => setTimeout(resolve, 500));
          }
          const c = document.createElement("input");
          c.type = "hidden";
          c.name = "cf-response";
          c.value = token;
          document.body.appendChild(c);
        }
        waitForToken();
      });

      await page.goto(url, { waitUntil: 'domcontentloaded' });
      await page.waitForSelector('[name="cf-response"]', { timeout: 45000 });

      const token = await page.evaluate(() => {
        const input = document.querySelector('[name="cf-response"]');
        return input ? input.value : null;
      });

      isResolved = true;
      clearTimeout(timeoutId);
      await context.close();
      activeRequests--;

      if (!token) return res.status(500).json({ code: 500, message: "Failed to resolve Turnstile token" });
      return res.json({ code: 200, token });

    } else if (mode === 'source') {
      // Scrape HTML content after WAF is bypassed
      let html = null;
      page.on('response', async (response) => {
        if ([200, 302].includes(response.status()) && [url, url + '/'].includes(response.url())) {
          await page.waitForNavigation({ waitUntil: 'load', timeout: 5000 }).catch(() => {});
          html = await page.content();
          isResolved = true;
          clearTimeout(timeoutId);
          await context.close();
          activeRequests--;
          res.json({ code: 200, source: html });
        }
      });

      await page.goto(url, { waitUntil: 'domcontentloaded' });

    } else {
      // Default: waf-session mode - harvest cookies and headers
      let acceptLanguage = await page.evaluate(async () => {
        return fetch("https://httpbin.org/get")
          .then(res => res.json())
          .then(res => res.headers["Accept-Language"] || res.headers["accept-language"])
          .catch(() => "en-US,en;q=0.9");
      });

      page.on('response', async (response) => {
        if ([200, 302].includes(response.status()) && [url, url + '/'].includes(response.url())) {
          await page.waitForNavigation({ waitUntil: 'load', timeout: 5000 }).catch(() => {});
          const cookies = await page.cookies();
          let headers = response.request().headers();
          
          // Clean up headers not required for secondary TLS requests
          delete headers["content-type"];
          delete headers["accept-encoding"];
          delete headers["accept"];
          delete headers["content-length"];
          headers["accept-language"] = acceptLanguage;

          isResolved = true;
          clearTimeout(timeoutId);
          await context.close();
          activeRequests--;
          res.json({ code: 200, cookies, headers });
        }
      });

      await page.goto(url, { waitUntil: 'domcontentloaded' });
    }

  } catch (error) {
    console.error(`[SERVER] Error processing request:`, error);
    if (!isResolved) {
      clearTimeout(timeoutId);
      if (context) await context.close().catch(() => {});
      activeRequests--;
      res.status(500).json({ code: 500, message: error.message });
    }
  }
});

// Start the server and launch browser
app.listen(PORT, async () => {
  console.log(`\n======================================================`);
  console.log(`[SERVER] Anti-Cloudflare Server running on http://localhost:${PORT}`);
  console.log(`======================================================\n`);
  await initBrowser();
});
