// ===================================
// STRESS TESTING TOOL - MAIN SCRIPT
// Enhanced with Crawler & Advanced Features
// ===================================

// ===================================
// WEBSITE CRAWLER CLASS
// ===================================
class WebsiteCrawler {
  constructor() {
    this.visitedUrls = new Set();
    this.urlQueue = [];
  }

  extractLinks(html, baseUrl) {
    const links = [];
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    const anchorTags = doc.querySelectorAll("a[href]");

    const baseUrlObj = new URL(baseUrl);

    anchorTags.forEach((anchor) => {
      try {
        const href = anchor.getAttribute("href");
        if (
          !href ||
          href.startsWith("#") ||
          href.startsWith("javascript:") ||
          href.startsWith("mailto:")
        ) {
          return;
        }

        const absoluteUrl = new URL(href, baseUrl);

        // Only include links from the same domain if configured
        if (absoluteUrl.hostname === baseUrlObj.hostname) {
          const urlString = absoluteUrl.href;
          if (!this.visitedUrls.has(urlString)) {
            links.push(urlString);
          }
        }
      } catch (e) {
        // Invalid URL, skip
      }
    });

    return links;
  }

  getNextUrl(currentUrl, html, config) {
    // Extract links from current page
    const links = this.extractLinks(html, currentUrl);

    // Add new links to queue (limit per page)
    const linksToAdd = links.slice(0, config.linksPerPage || 10);
    linksToAdd.forEach((link) => {
      if (!this.visitedUrls.has(link) && this.urlQueue.length < 100) {
        this.urlQueue.push(link);
      }
    });

    // Mark current URL as visited
    this.visitedUrls.add(currentUrl);

    // Get next URL from queue
    if (this.urlQueue.length > 0) {
      const randomIndex = Math.floor(Math.random() * this.urlQueue.length);
      return this.urlQueue.splice(randomIndex, 1)[0];
    }

    return null;
  }

  reset() {
    this.visitedUrls.clear();
    this.urlQueue = [];
  }
}

// ===================================
// UTILITY FUNCTIONS
// ===================================
function calculatePercentile(arr, percentile) {
  if (arr.length === 0) return 0;
  const sorted = [...arr].sort((a, b) => a - b);
  const index = Math.ceil((percentile / 100) * sorted.length) - 1;
  return sorted[Math.max(0, index)];
}

function categorizeError(statusCode, errorMessage) {
  if (statusCode >= 400 && statusCode < 500) return "4xx";
  if (statusCode >= 500) return "5xx";
  if (errorMessage && errorMessage.includes("timeout")) return "timeout";
  return "network";
}

function formatBytes(bytes) {
  if (bytes === 0) return "0 B";
  const k = 1024;
  const sizes = ["B", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
}

// ===================================
// MAIN STRESS TESTING TOOL CLASS
// ===================================
class StressTestingTool {
  constructor() {
    this.config = {
      targetUrl: "",
      userCount: 100,
      duration: 60,
      trafficPattern: "steady",
      httpMethod: "GET",
      customHeaders: {},
      requestBody: null,
      thinkTime: 1000,
      proxyUrl:
        window.location.protocol === "file:" ||
        window.location.hostname === "localhost" ||
        window.location.hostname === "127.0.0.1"
          ? "http://localhost:3000"
          : "/proxy",

      // Crawler settings
      crawlerEnabled: false,
      crawlDepth: 2,
      linksPerPage: 10,
      stayOnDomain: true,
    };

    this.state = {
      status: "idle",
      startTime: null,
      pauseTime: null,
      elapsedTime: 0,
      activeUsers: 0,
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      responseTimes: [],
      requestsPerSecond: [],
      workers: [],
      updateInterval: null,
      chartUpdateInterval: null,
      userErrorData: [],
      errorThreshold: null,

      // Enhanced metrics
      errorsByCategory: {
        "4xx": 0,
        "5xx": 0,
        timeout: 0,
        network: 0,
      },
      totalBytesSent: 0,
      totalBytesReceived: 0,
      requestHistory: [],

      // Percentile tracking
      percentiles: {
        p50: 0,
        p95: 0,
        p99: 0,
      },
    };

    this.crawler = new WebsiteCrawler();
    this.charts = {
      rps: null,
      responseTime: null,
      userError: null,
    };

    // Test presets
    this.presets = {
      light: { userCount: 10, duration: 30, trafficPattern: "steady" },
      medium: { userCount: 100, duration: 60, trafficPattern: "random" },
      heavy: { userCount: 500, duration: 120, trafficPattern: "rampup" },
      spike: { userCount: 200, duration: 60, trafficPattern: "burst" },
    };

    this.init();
  }

  init() {
    this.bindElements();
    this.attachEventListeners();
    this.initializeCharts();
    this.loadTheme();
    this.loadSavedConfigs();
    this.setupKeyboardShortcuts();
    this.fetchGitInfo();
  }

  bindElements() {
    // Form inputs
    this.elements = {
      targetUrl: document.getElementById("targetUrl"),
      userCount: document.getElementById("userCount"),
      userCountValue: document.getElementById("userCountValue"),
      duration: document.getElementById("duration"),
      durationValue: document.getElementById("durationValue"),
      trafficPattern: document.getElementById("trafficPattern"),
      httpMethod: document.getElementById("httpMethod"),
      customHeaders: document.getElementById("customHeaders"),
      requestBody: document.getElementById("requestBody"),
      thinkTime: document.getElementById("thinkTime"),
      thinkTimeValue: document.getElementById("thinkTimeValue"),

      // Crawler controls
      crawlerEnabled: document.getElementById("crawlerEnabled"),
      crawlDepth: document.getElementById("crawlDepth"),
      crawlDepthValue: document.getElementById("crawlDepthValue"),
      linksPerPage: document.getElementById("linksPerPage"),
      linksPerPageValue: document.getElementById("linksPerPageValue"),

      // Controls
      startBtn: document.getElementById("startBtn"),
      pauseBtn: document.getElementById("pauseBtn"),
      stopBtn: document.getElementById("stopBtn"),
      statusBadge: document.getElementById("statusBadge"),
      progressBar: document.getElementById("progressBar"),

      // Statistics
      elapsedTime: document.getElementById("elapsedTime"),
      remainingTime: document.getElementById("remainingTime"),
      activeUsers: document.getElementById("activeUsers"),
      totalRequests: document.getElementById("totalRequests"),
      requestsPerSec: document.getElementById("requestsPerSec"),
      successRate: document.getElementById("successRate"),
      failedRequests: document.getElementById("failedRequests"),
      avgResponseTime: document.getElementById("avgResponseTime"),

      // Enhanced metrics
      p50ResponseTime: document.getElementById("p50ResponseTime"),
      p95ResponseTime: document.getElementById("p95ResponseTime"),
      p99ResponseTime: document.getElementById("p99ResponseTime"),
      errors4xx: document.getElementById("errors4xx"),
      errors5xx: document.getElementById("errors5xx"),
      errorsTimeout: document.getElementById("errorsTimeout"),
      errorsNetwork: document.getElementById("errorsNetwork"),
      totalBandwidth: document.getElementById("totalBandwidth"),

      // Request history
      requestHistoryBody: document.getElementById("requestHistoryBody"),

      // Results
      resultsPanel: document.getElementById("resultsPanel"),
      resultsTableBody: document.getElementById("resultsTableBody"),
      exportJsonBtn: document.getElementById("exportJsonBtn"),
      exportCsvBtn: document.getElementById("exportCsvBtn"),

      // Advanced options
      advancedToggle: document.getElementById("advancedToggle"),
      advancedContent: document.getElementById("advancedContent"),

      // Theme & presets
      themeToggle: document.getElementById("themeToggle"),
      presetSelect: document.getElementById("presetSelect"),
      saveConfigBtn: document.getElementById("saveConfigBtn"),

      // Git Info
      gitInfo: document.getElementById("gitInfo"),
      gitCommit: document.getElementById("gitCommit"),
      gitDate: document.getElementById("gitDate"),
    };
  }

  attachEventListeners() {
    // Range inputs
    this.elements.userCount.addEventListener("input", (e) => {
      this.elements.userCountValue.textContent = e.target.value;
    });

    this.elements.duration.addEventListener("input", (e) => {
      this.elements.durationValue.textContent = e.target.value;
    });

    this.elements.thinkTime.addEventListener("input", (e) => {
      this.elements.thinkTimeValue.textContent = e.target.value;
    });

    if (this.elements.crawlDepth) {
      this.elements.crawlDepth.addEventListener("input", (e) => {
        this.elements.crawlDepthValue.textContent = e.target.value;
      });
    }

    if (this.elements.linksPerPage) {
      this.elements.linksPerPage.addEventListener("input", (e) => {
        this.elements.linksPerPageValue.textContent = e.target.value;
      });
    }

    // Control buttons
    this.elements.startBtn.addEventListener("click", () => this.startTest());
    this.elements.pauseBtn.addEventListener("click", () => this.pauseTest());
    this.elements.stopBtn.addEventListener("click", () => this.stopTest());

    // Export buttons
    this.elements.exportJsonBtn.addEventListener("click", () =>
      this.exportResults("json")
    );
    this.elements.exportCsvBtn.addEventListener("click", () =>
      this.exportResults("csv")
    );

    // Advanced options accordion
    this.elements.advancedToggle.addEventListener("click", () => {
      this.elements.advancedToggle.classList.toggle("active");
      this.elements.advancedContent.classList.toggle("active");
    });

    // Theme toggle
    if (this.elements.themeToggle) {
      this.elements.themeToggle.addEventListener("click", () =>
        this.toggleTheme()
      );
    }

    // Preset selector
    if (this.elements.presetSelect) {
      this.elements.presetSelect.addEventListener("change", (e) =>
        this.loadPreset(e.target.value)
      );
    }

    // Save config
    if (this.elements.saveConfigBtn) {
      this.elements.saveConfigBtn.addEventListener("click", () =>
        this.saveConfig()
      );
    }
  }

  setupKeyboardShortcuts() {
    document.addEventListener("keydown", (e) => {
      // Don't trigger if user is typing in an input
      if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA")
        return;

      switch (e.key.toLowerCase()) {
        case "s":
          if (this.state.status === "idle") this.startTest();
          break;
        case "p":
          if (this.state.status === "running" || this.state.status === "paused")
            this.pauseTest();
          break;
        case "x":
          if (this.state.status === "running" || this.state.status === "paused")
            this.stopTest();
          break;
      }
    });
  }

  loadTheme() {
    const savedTheme = localStorage.getItem("stressTestTheme") || "dark";
    document.documentElement.setAttribute("data-theme", savedTheme);
  }

  toggleTheme() {
    const currentTheme =
      document.documentElement.getAttribute("data-theme") || "dark";
    const newTheme = currentTheme === "dark" ? "light" : "dark";
    document.documentElement.setAttribute("data-theme", newTheme);
    localStorage.setItem("stressTestTheme", newTheme);

    // Update chart colors
    this.updateChartTheme();
  }

  updateChartTheme() {
    const isDark =
      document.documentElement.getAttribute("data-theme") === "dark";
    const textColor = isDark ? "#94a3b8" : "#475569";
    const gridColor = isDark
      ? "rgba(148, 163, 184, 0.1)"
      : "rgba(148, 163, 184, 0.2)";

    Object.values(this.charts).forEach((chart) => {
      if (chart) {
        chart.options.scales.x.ticks.color = textColor;
        chart.options.scales.x.grid.color = gridColor;
        chart.options.scales.y.ticks.color = textColor;
        chart.options.scales.y.grid.color = gridColor;
        if (chart.options.scales.y1) {
          chart.options.scales.y1.ticks.color = textColor;
        }
        chart.update("none");
      }
    });
  }

  loadSavedConfigs() {
    const saved = localStorage.getItem("stressTestConfigs");
    if (saved) {
      try {
        const configs = JSON.parse(saved);
        // Add to preset select if exists
        if (this.elements.presetSelect) {
          Object.keys(configs).forEach((name) => {
            const option = document.createElement("option");
            option.value = `saved_${name}`;
            option.textContent = `💾 ${name}`;
            this.elements.presetSelect.appendChild(option);
          });
        }
      } catch (e) {
        console.error("Failed to load saved configs:", e);
      }
    }
  }

  loadPreset(presetName) {
    if (!presetName) return;

    let config;
    if (presetName.startsWith("saved_")) {
      const saved = JSON.parse(
        localStorage.getItem("stressTestConfigs") || "{}"
      );
      config = saved[presetName.replace("saved_", "")];
    } else {
      config = this.presets[presetName];
    }

    if (config) {
      this.elements.userCount.value = config.userCount;
      this.elements.userCountValue.textContent = config.userCount;
      this.elements.duration.value = config.duration;
      this.elements.durationValue.textContent = config.duration;
      this.elements.trafficPattern.value = config.trafficPattern;
    }
  }

  saveConfig() {
    const name = prompt("Enter a name for this configuration:");
    if (!name) return;

    const config = {
      userCount: parseInt(this.elements.userCount.value),
      duration: parseInt(this.elements.duration.value),
      trafficPattern: this.elements.trafficPattern.value,
      targetUrl: this.elements.targetUrl.value,
    };

    const saved = JSON.parse(localStorage.getItem("stressTestConfigs") || "{}");
    saved[name] = config;
    localStorage.setItem("stressTestConfigs", JSON.stringify(saved));

    alert(`Configuration "${name}" saved!`);
    location.reload(); // Reload to update preset list
  }

  async fetchGitInfo() {
    try {
      // Ensure we don't have double slashes if proxyUrl ends with slash (it shouldn't based on init logic)
      const url = `${this.config.proxyUrl}/git-info`;
      const response = await fetch(url);
      if (response.ok) {
        const data = await response.json();
        if (data.commit && data.date && data.commit !== 'Unknown') {
          if (this.elements.gitCommit) this.elements.gitCommit.textContent = data.commit;
          if (this.elements.gitDate) {
            let dateStr = data.date;
            // Shorten to match screenshot style (approximate)
            dateStr = dateStr.replace(/ days? ago/, 'd ago')
                           .replace(/ hours? ago/, 'h ago')
                           .replace(/ minutes? ago/, 'm ago')
                           .replace(/ seconds? ago/, 's ago');
            this.elements.gitDate.textContent = dateStr;
          }
          if (this.elements.gitInfo) this.elements.gitInfo.style.display = 'flex';
        }
      }
    } catch (e) {
      console.error('Failed to fetch git info:', e);
    }
  }

  initializeCharts() {
    const isDark =
      document.documentElement.getAttribute("data-theme") === "dark";
    const textColor = isDark ? "#94a3b8" : "#475569";
    const gridColor = isDark
      ? "rgba(148, 163, 184, 0.1)"
      : "rgba(148, 163, 184, 0.2)";

    const chartOptions = {
      responsive: true,
      maintainAspectRatio: false,
      plugins: {
        legend: {
          display: false,
        },
      },
      scales: {
        x: {
          grid: {
            color: gridColor,
          },
          ticks: {
            color: textColor,
          },
        },
        y: {
          grid: {
            color: gridColor,
          },
          ticks: {
            color: textColor,
          },
          beginAtZero: true,
        },
      },
    };

    // RPS Chart
    const rpsCtx = document.getElementById("rpsChart").getContext("2d");
    this.charts.rps = new Chart(rpsCtx, {
      type: "line",
      data: {
        labels: [],
        datasets: [
          {
            label: "Requests per Second",
            data: [],
            borderColor: "#6366f1",
            backgroundColor: "rgba(99, 102, 241, 0.1)",
            borderWidth: 2,
            fill: true,
            tension: 0.4,
          },
        ],
      },
      options: {
        ...chartOptions,
        plugins: {
          ...chartOptions.plugins,
          title: {
            display: true,
            text: "Requests per Second",
            color: textColor,
            font: {
              size: 14,
              weight: 600,
            },
          },
        },
      },
    });

    // Response Time Chart
    const responseTimeCtx = document
      .getElementById("responseTimeChart")
      .getContext("2d");
    this.charts.responseTime = new Chart(responseTimeCtx, {
      type: "line",
      data: {
        labels: [],
        datasets: [
          {
            label: "Average Response Time (ms)",
            data: [],
            borderColor: "#f59e0b",
            backgroundColor: "rgba(245, 158, 11, 0.1)",
            borderWidth: 2,
            fill: true,
            tension: 0.4,
          },
        ],
      },
      options: {
        ...chartOptions,
        plugins: {
          ...chartOptions.plugins,
          title: {
            display: true,
            text: "Average Response Time",
            color: textColor,
            font: {
              size: 14,
              weight: 600,
            },
          },
        },
      },
    });

    // User/Error Correlation Chart
    const userErrorCtx = document
      .getElementById("userErrorChart")
      .getContext("2d");
    this.charts.userError = new Chart(userErrorCtx, {
      type: "line",
      data: {
        labels: [],
        datasets: [
          {
            label: "Active Users",
            data: [],
            borderColor: "#3b82f6",
            backgroundColor: "rgba(59, 130, 246, 0.1)",
            borderWidth: 2,
            fill: true,
            tension: 0.4,
            yAxisID: "y",
          },
          {
            label: "Error Rate (%)",
            data: [],
            borderColor: "#ef4444",
            backgroundColor: "rgba(239, 68, 68, 0.2)",
            borderWidth: 2,
            fill: true,
            tension: 0.4,
            yAxisID: "y1",
          },
        ],
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        interaction: {
          mode: "index",
          intersect: false,
        },
        plugins: {
          legend: {
            display: true,
            labels: {
              color: textColor,
              font: {
                size: 12,
                weight: 600,
              },
            },
          },
          title: {
            display: true,
            text: "User Load vs Error Rate",
            color: textColor,
            font: {
              size: 14,
              weight: 600,
            },
          },
        },
        scales: {
          x: {
            grid: {
              color: gridColor,
            },
            ticks: {
              color: textColor,
            },
          },
          y: {
            type: "linear",
            display: true,
            position: "left",
            title: {
              display: true,
              text: "Active Users",
              color: "#3b82f6",
              font: {
                size: 12,
                weight: 600,
              },
            },
            grid: {
              color: gridColor,
            },
            ticks: {
              color: textColor,
            },
            beginAtZero: true,
          },
          y1: {
            type: "linear",
            display: true,
            position: "right",
            title: {
              display: true,
              text: "Error Rate (%)",
              color: "#ef4444",
              font: {
                size: 12,
                weight: 600,
              },
            },
            grid: {
              drawOnChartArea: false,
            },
            ticks: {
              color: textColor,
            },
            beginAtZero: true,
            max: 100,
          },
        },
      },
    });
  }

  async startTest() {
    if (!this.validateConfig()) {
      return;
    }

    this.gatherConfig();
    this.resetState();
    this.updateStatus("running");
    this.state.startTime = Date.now();

    // Update UI
    this.elements.startBtn.disabled = true;
    this.elements.pauseBtn.disabled = false;
    this.elements.stopBtn.disabled = false;

    // Start workers
    this.startWorkers();

    // Start update intervals
    this.state.updateInterval = setInterval(() => this.updateStatistics(), 100);
    this.state.chartUpdateInterval = setInterval(
      () => this.updateCharts(),
      1000
    );
  }

  pauseTest() {
    if (this.state.status === "running") {
      this.updateStatus("paused");
      this.state.pauseTime = Date.now();
      this.stopWorkers();
      this.elements.pauseBtn.textContent = "▶️ Resume";
    } else if (this.state.status === "paused") {
      this.updateStatus("running");
      const pauseDuration = Date.now() - this.state.pauseTime;
      this.state.startTime += pauseDuration;
      this.startWorkers();
      this.elements.pauseBtn.textContent = "⏸️ Pause";
    }
  }

  stopTest() {
    this.updateStatus("stopped");
    this.stopWorkers();
    clearInterval(this.state.updateInterval);
    clearInterval(this.state.chartUpdateInterval);

    // Update UI
    this.elements.startBtn.disabled = false;
    this.elements.pauseBtn.disabled = true;
    this.elements.stopBtn.disabled = true;
    this.elements.pauseBtn.textContent = "⏸️ Pause";

    // Calculate final percentiles
    this.calculatePercentiles();

    // Show results
    this.displayResults();
  }

  validateConfig() {
    const url = this.elements.targetUrl.value.trim();
    if (!url) {
      alert("Please enter a target URL");
      return false;
    }

    try {
      new URL(url);
    } catch (e) {
      alert("Please enter a valid URL");
      return false;
    }

    const headersText = this.elements.customHeaders.value.trim();
    if (headersText) {
      try {
        JSON.parse(headersText);
      } catch (e) {
        alert("Custom headers must be valid JSON");
        return false;
      }
    }

    const bodyText = this.elements.requestBody.value.trim();
    if (bodyText) {
      try {
        JSON.parse(bodyText);
      } catch (e) {
        alert("Request body must be valid JSON");
        return false;
      }
    }

    return true;
  }

  gatherConfig() {
    this.config.targetUrl = this.elements.targetUrl.value.trim();
    this.config.userCount = parseInt(this.elements.userCount.value);
    this.config.duration = parseInt(this.elements.duration.value);
    this.config.trafficPattern = this.elements.trafficPattern.value;
    this.config.httpMethod = this.elements.httpMethod.value;
    this.config.thinkTime = parseInt(this.elements.thinkTime.value);

    const headersText = this.elements.customHeaders.value.trim();
    this.config.customHeaders = headersText ? JSON.parse(headersText) : {};

    const bodyText = this.elements.requestBody.value.trim();
    this.config.requestBody = bodyText ? JSON.parse(bodyText) : null;

    // Crawler config
    if (this.elements.crawlerEnabled) {
      this.config.crawlerEnabled = this.elements.crawlerEnabled.checked;
      this.config.crawlDepth = parseInt(this.elements.crawlDepth?.value || 2);
      this.config.linksPerPage = parseInt(
        this.elements.linksPerPage?.value || 10
      );
    }
  }

  resetState() {
    this.state.elapsedTime = 0;
    this.state.activeUsers = 0;
    this.state.totalRequests = 0;
    this.state.successfulRequests = 0;
    this.state.failedRequests = 0;
    this.state.responseTimes = [];
    this.state.requestsPerSecond = [];
    this.state.workers = [];
    this.state.userErrorData = [];
    this.state.errorThreshold = null;
    this.state.errorsByCategory = {
      "4xx": 0,
      "5xx": 0,
      timeout: 0,
      network: 0,
    };
    this.state.totalBytesSent = 0;
    this.state.totalBytesReceived = 0;
    this.state.requestHistory = [];
    this.state.percentiles = { p50: 0, p95: 0, p99: 0 };

    // Reset crawler
    this.crawler.reset();
    if (this.config.crawlerEnabled) {
      this.crawler.urlQueue.push(this.config.targetUrl);
    }

    // Reset charts
    this.charts.rps.data.labels = [];
    this.charts.rps.data.datasets[0].data = [];
    this.charts.responseTime.data.labels = [];
    this.charts.responseTime.data.datasets[0].data = [];
    this.charts.userError.data.labels = [];
    this.charts.userError.data.datasets[0].data = [];
    this.charts.userError.data.datasets[1].data = [];
    this.charts.rps.update("none");
    this.charts.responseTime.update("none");
    this.charts.userError.update("none");

    // Clear request history table
    if (this.elements.requestHistoryBody) {
      this.elements.requestHistoryBody.innerHTML = "";
    }

    // Hide results panel
    this.elements.resultsPanel.style.display = "none";
  }

  startWorkers() {
    const pattern = this.config.trafficPattern;

    switch (pattern) {
      case "steady":
        this.startSteadyLoad();
        break;
      case "burst":
        this.startBurstLoad();
        break;
      case "rampup":
        this.startRampUpLoad();
        break;
      case "random":
        this.startRandomLoad();
        break;
    }
  }

  startSteadyLoad() {
    const delayBetweenUsers = 100;

    for (let i = 0; i < this.config.userCount; i++) {
      setTimeout(() => {
        if (this.state.status === "running") {
          this.createWorker(i);
        }
      }, i * delayBetweenUsers);
    }
  }

  startBurstLoad() {
    const burstSize = Math.ceil(this.config.userCount / 5);
    const burstInterval = (this.config.duration * 1000) / 5;

    for (let burst = 0; burst < 5; burst++) {
      setTimeout(() => {
        if (this.state.status === "running") {
          for (let i = 0; i < burstSize; i++) {
            this.createWorker(burst * burstSize + i);
          }
        }
      }, burst * burstInterval);
    }
  }

  startRampUpLoad() {
    const totalTime = this.config.duration * 1000;
    const timePerUser = totalTime / this.config.userCount;

    for (let i = 0; i < this.config.userCount; i++) {
      setTimeout(() => {
        if (this.state.status === "running") {
          this.createWorker(i);
        }
      }, i * timePerUser);
    }
  }

  startRandomLoad() {
    const maxDelay = (this.config.duration * 1000) / 2;

    for (let i = 0; i < this.config.userCount; i++) {
      const randomDelay = Math.random() * maxDelay;
      setTimeout(() => {
        if (this.state.status === "running") {
          this.createWorker(i);
        }
      }, randomDelay);
    }
  }

  createWorker(id) {
    const worker = {
      id: id,
      active: true,
      requestCount: 0,
      currentUrl: this.config.targetUrl,
      crawlDepth: 0,
    };

    this.state.workers.push(worker);
    this.state.activeUsers++;
    this.runWorker(worker);
  }

  async runWorker(worker) {
    const endTime = this.state.startTime + this.config.duration * 1000;

    while (
      worker.active &&
      this.state.status === "running" &&
      Date.now() < endTime
    ) {
      await this.makeRequest(worker);

      // Think time
      if (this.config.thinkTime > 0) {
        await this.sleep(this.config.thinkTime);
      }
    }

    // Worker finished
    worker.active = false;
    this.state.activeUsers--;

    // Check if all workers are done
    if (this.state.activeUsers === 0 && this.state.status === "running") {
      this.stopTest();
    }
  }

  async makeRequest(worker) {
    const startTime = performance.now();
    const requestUrl = worker.currentUrl;

    try {
      const proxyPayload = {
        targetUrl: requestUrl,
        method: this.config.httpMethod,
        headers: this.config.customHeaders,
        body: this.config.requestBody,
      };

      // Estimate request size
      const requestSize = JSON.stringify(proxyPayload).length;
      this.state.totalBytesSent += requestSize;

      const response = await fetch(this.config.proxyUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(proxyPayload),
      });

      const endTime = performance.now();
      const responseTime = endTime - startTime;

      this.state.totalRequests++;
      worker.requestCount++;

      const proxyResponse = await response.json();

      // Track response size
      if (proxyResponse.body) {
        this.state.totalBytesReceived += proxyResponse.body.length;
      }

      const isSuccess =
        proxyResponse.success &&
        proxyResponse.statusCode >= 200 &&
        proxyResponse.statusCode < 400;

      if (isSuccess) {
        this.state.successfulRequests++;
      } else {
        this.state.failedRequests++;
        const category = categorizeError(
          proxyResponse.statusCode,
          proxyResponse.error
        );
        this.state.errorsByCategory[category]++;
      }

      const actualResponseTime = proxyResponse.responseTime || responseTime;
      this.state.responseTimes.push(actualResponseTime);

      // Keep only last 1000 response times
      if (this.state.responseTimes.length > 1000) {
        this.state.responseTimes.shift();
      }

      // Add to request history
      this.addToRequestHistory({
        url: requestUrl,
        status: proxyResponse.statusCode,
        responseTime: Math.round(actualResponseTime),
        success: isSuccess,
        timestamp: new Date().toLocaleTimeString(),
      });

      // Crawler: Get next URL if enabled
      if (
        this.config.crawlerEnabled &&
        isSuccess &&
        proxyResponse.body &&
        worker.crawlDepth < this.config.crawlDepth
      ) {
        const nextUrl = this.crawler.getNextUrl(
          requestUrl,
          proxyResponse.body,
          this.config
        );
        if (nextUrl) {
          worker.currentUrl = nextUrl;
          worker.crawlDepth++;
        }
      }
    } catch (error) {
      const endTime = performance.now();
      const responseTime = endTime - startTime;

      this.state.totalRequests++;
      this.state.failedRequests++;
      this.state.responseTimes.push(responseTime);
      this.state.errorsByCategory["network"]++;
      worker.requestCount++;

      this.addToRequestHistory({
        url: requestUrl,
        status: 0,
        responseTime: Math.round(responseTime),
        success: false,
        timestamp: new Date().toLocaleTimeString(),
        error: error.message,
      });
    }
  }

  addToRequestHistory(request) {
    this.state.requestHistory.unshift(request);

    // Keep only last 100
    if (this.state.requestHistory.length > 100) {
      this.state.requestHistory.pop();
    }

    // Update UI table
    if (this.elements.requestHistoryBody) {
      const row = document.createElement("tr");
      row.className = request.success ? "success-row" : "error-row";
      row.innerHTML = `
        <td>${request.timestamp}</td>
        <td class="url-cell" title="${request.url}">${this.truncateUrl(
        request.url
      )}</td>
        <td><span class="status-code ${
          request.success ? "success" : "error"
        }">${request.status}</span></td>
        <td>${request.responseTime}ms</td>
      `;

      this.elements.requestHistoryBody.insertBefore(
        row,
        this.elements.requestHistoryBody.firstChild
      );

      // Keep only 100 rows in DOM
      while (this.elements.requestHistoryBody.children.length > 100) {
        this.elements.requestHistoryBody.removeChild(
          this.elements.requestHistoryBody.lastChild
        );
      }
    }
  }

  truncateUrl(url) {
    if (url.length > 50) {
      return url.substring(0, 47) + "...";
    }
    return url;
  }

  stopWorkers() {
    this.state.workers.forEach((worker) => {
      worker.active = false;
    });
  }

  calculatePercentiles() {
    if (this.state.responseTimes.length > 0) {
      this.state.percentiles.p50 = Math.round(
        calculatePercentile(this.state.responseTimes, 50)
      );
      this.state.percentiles.p95 = Math.round(
        calculatePercentile(this.state.responseTimes, 95)
      );
      this.state.percentiles.p99 = Math.round(
        calculatePercentile(this.state.responseTimes, 99)
      );
    }
  }

  updateStatistics() {
    const now = Date.now();
    const elapsed = Math.floor((now - this.state.startTime) / 1000);
    const remaining = Math.max(0, this.config.duration - elapsed);
    const progress = Math.min(100, (elapsed / this.config.duration) * 100);

    // Update time displays
    this.elements.elapsedTime.textContent = `${elapsed}s`;
    this.elements.remainingTime.textContent = `${remaining}s`;
    this.elements.progressBar.style.width = `${progress}%`;

    // Update statistics
    this.elements.activeUsers.textContent = this.state.activeUsers;
    this.elements.totalRequests.textContent =
      this.state.totalRequests.toLocaleString();
    this.elements.failedRequests.textContent =
      this.state.failedRequests.toLocaleString();

    // Calculate RPS
    const rps =
      elapsed > 0 ? Math.round(this.state.totalRequests / elapsed) : 0;
    this.elements.requestsPerSec.textContent = rps;

    // Calculate success rate
    const successRate =
      this.state.totalRequests > 0
        ? (
            (this.state.successfulRequests / this.state.totalRequests) *
            100
          ).toFixed(1)
        : 0;
    this.elements.successRate.textContent = `${successRate}%`;

    // Calculate average response time
    const avgResponseTime =
      this.state.responseTimes.length > 0
        ? Math.round(
            this.state.responseTimes.reduce((a, b) => a + b, 0) /
              this.state.responseTimes.length
          )
        : 0;
    this.elements.avgResponseTime.textContent = `${avgResponseTime}ms`;

    // Update enhanced metrics
    if (this.elements.p50ResponseTime) {
      const p50 = Math.round(calculatePercentile(this.state.responseTimes, 50));
      const p95 = Math.round(calculatePercentile(this.state.responseTimes, 95));
      const p99 = Math.round(calculatePercentile(this.state.responseTimes, 99));

      this.elements.p50ResponseTime.textContent = `${p50}ms`;
      this.elements.p95ResponseTime.textContent = `${p95}ms`;
      this.elements.p99ResponseTime.textContent = `${p99}ms`;
    }

    if (this.elements.errors4xx) {
      this.elements.errors4xx.textContent = this.state.errorsByCategory["4xx"];
      this.elements.errors5xx.textContent = this.state.errorsByCategory["5xx"];
      this.elements.errorsTimeout.textContent =
        this.state.errorsByCategory["timeout"];
      this.elements.errorsNetwork.textContent =
        this.state.errorsByCategory["network"];
    }

    if (this.elements.totalBandwidth) {
      const totalBytes =
        this.state.totalBytesSent + this.state.totalBytesReceived;
      this.elements.totalBandwidth.textContent = formatBytes(totalBytes);
    }
  }

  updateCharts() {
    const now = Date.now();
    const elapsed = Math.floor((now - this.state.startTime) / 1000);

    // Calculate current RPS
    const currentRps =
      this.state.totalRequests > 0 && elapsed > 0
        ? Math.round(this.state.totalRequests / elapsed)
        : 0;

    // Calculate current average response time
    const recentResponseTimes = this.state.responseTimes.slice(-100);
    const currentAvgResponseTime =
      recentResponseTimes.length > 0
        ? Math.round(
            recentResponseTimes.reduce((a, b) => a + b, 0) /
              recentResponseTimes.length
          )
        : 0;

    // Update RPS chart
    this.charts.rps.data.labels.push(`${elapsed}s`);
    this.charts.rps.data.datasets[0].data.push(currentRps);

    if (this.charts.rps.data.labels.length > 60) {
      this.charts.rps.data.labels.shift();
      this.charts.rps.data.datasets[0].data.shift();
    }

    this.charts.rps.update("none");

    // Update Response Time chart
    this.charts.responseTime.data.labels.push(`${elapsed}s`);
    this.charts.responseTime.data.datasets[0].data.push(currentAvgResponseTime);

    if (this.charts.responseTime.data.labels.length > 60) {
      this.charts.responseTime.data.labels.shift();
      this.charts.responseTime.data.datasets[0].data.shift();
    }

    this.charts.responseTime.update("none");

    // Calculate current error rate
    const currentErrorRate =
      this.state.totalRequests > 0
        ? (
            (this.state.failedRequests / this.state.totalRequests) *
            100
          ).toFixed(1)
        : 0;

    // Update User/Error chart
    this.charts.userError.data.labels.push(`${elapsed}s`);
    this.charts.userError.data.datasets[0].data.push(this.state.activeUsers);
    this.charts.userError.data.datasets[1].data.push(
      parseFloat(currentErrorRate)
    );

    // Track user/error data
    this.state.userErrorData.push({
      time: elapsed,
      users: this.state.activeUsers,
      errorRate: parseFloat(currentErrorRate),
      failedRequests: this.state.failedRequests,
    });

    // Detect error threshold
    if (
      this.state.errorThreshold === null &&
      this.state.failedRequests > 0 &&
      this.state.activeUsers > 0
    ) {
      this.state.errorThreshold = {
        users: this.state.activeUsers,
        time: elapsed,
        errorRate: parseFloat(currentErrorRate),
      };
    }

    if (this.charts.userError.data.labels.length > 60) {
      this.charts.userError.data.labels.shift();
      this.charts.userError.data.datasets[0].data.shift();
      this.charts.userError.data.datasets[1].data.shift();
    }

    this.charts.userError.update("none");
  }

  updateStatus(status) {
    this.state.status = status;
    const badge = this.elements.statusBadge;

    badge.className = "status-badge";

    switch (status) {
      case "idle":
        badge.classList.add("status-idle");
        badge.textContent = "Idle";
        break;
      case "running":
        badge.classList.add("status-running");
        badge.textContent = "Running";
        break;
      case "paused":
        badge.classList.add("status-paused");
        badge.textContent = "Paused";
        break;
      case "stopped":
        badge.classList.add("status-idle");
        badge.textContent = "Completed";
        break;
    }
  }

  displayResults() {
    this.elements.resultsPanel.style.display = "block";

    const results = this.calculateResults();
    const tbody = this.elements.resultsTableBody;
    tbody.innerHTML = "";

    // Populate results table
    Object.entries(results).forEach(([key, value]) => {
      const row = document.createElement("tr");
      row.innerHTML = `
        <td><strong>${key}</strong></td>
        <td><code>${value}</code></td>
      `;
      tbody.appendChild(row);
    });

    // Scroll to results
    this.elements.resultsPanel.scrollIntoView({ behavior: "smooth" });
  }

  calculateResults() {
    const totalTime =
      this.state.elapsedTime ||
      Math.floor((Date.now() - this.state.startTime) / 1000);
    const successRate =
      this.state.totalRequests > 0
        ? (
            (this.state.successfulRequests / this.state.totalRequests) *
            100
          ).toFixed(2)
        : 0;

    const avgResponseTime =
      this.state.responseTimes.length > 0
        ? Math.round(
            this.state.responseTimes.reduce((a, b) => a + b, 0) /
              this.state.responseTimes.length
          )
        : 0;

    const minResponseTime =
      this.state.responseTimes.length > 0
        ? Math.round(Math.min(...this.state.responseTimes))
        : 0;

    const maxResponseTime =
      this.state.responseTimes.length > 0
        ? Math.round(Math.max(...this.state.responseTimes))
        : 0;

    const rps =
      totalTime > 0 ? (this.state.totalRequests / totalTime).toFixed(2) : 0;

    const results = {
      "Target URL": this.config.targetUrl,
      "Test Duration": `${totalTime} seconds`,
      "Concurrent Users": this.config.userCount,
      "Traffic Pattern": this.config.trafficPattern,
      "Crawler Mode": this.config.crawlerEnabled ? "Enabled" : "Disabled",
      "Total Requests": this.state.totalRequests.toLocaleString(),
      "Successful Requests": this.state.successfulRequests.toLocaleString(),
      "Failed Requests": this.state.failedRequests.toLocaleString(),
      "Success Rate": `${successRate}%`,
      "Requests per Second": rps,
      "Average Response Time": `${avgResponseTime}ms`,
      "Min Response Time": `${minResponseTime}ms`,
      "Max Response Time": `${maxResponseTime}ms`,
      "P50 Response Time": `${this.state.percentiles.p50}ms`,
      "P95 Response Time": `${this.state.percentiles.p95}ms`,
      "P99 Response Time": `${this.state.percentiles.p99}ms`,
      "4xx Errors": this.state.errorsByCategory["4xx"],
      "5xx Errors": this.state.errorsByCategory["5xx"],
      "Timeout Errors": this.state.errorsByCategory["timeout"],
      "Network Errors": this.state.errorsByCategory["network"],
      "Total Bandwidth": formatBytes(
        this.state.totalBytesSent + this.state.totalBytesReceived
      ),
      "Data Sent": formatBytes(this.state.totalBytesSent),
      "Data Received": formatBytes(this.state.totalBytesReceived),
      "HTTP Method": this.config.httpMethod,
      "Think Time": `${this.config.thinkTime}ms`,
      "Error Threshold": this.state.errorThreshold
        ? `${this.state.errorThreshold.users} users at ${this.state.errorThreshold.time}s (${this.state.errorThreshold.errorRate}% error rate)`
        : "No errors detected",
    };

    if (this.config.crawlerEnabled) {
      results["Unique URLs Visited"] = this.crawler.visitedUrls.size;
    }

    return results;
  }

  exportResults(format) {
    const results = this.calculateResults();
    const timestamp = new Date().toISOString().replace(/[:.]/g, "-");

    if (format === "json") {
      const data = {
        config: this.config,
        results: results,
        requestHistory: this.state.requestHistory.slice(0, 100),
        timestamp: new Date().toISOString(),
      };

      const blob = new Blob([JSON.stringify(data, null, 2)], {
        type: "application/json",
      });
      this.downloadFile(blob, `stress-test-results-${timestamp}.json`);
    } else if (format === "csv") {
      let csv = "Metric,Value\n";
      Object.entries(results).forEach(([key, value]) => {
        csv += `"${key}","${value}"\n`;
      });

      const blob = new Blob([csv], { type: "text/csv" });
      this.downloadFile(blob, `stress-test-results-${timestamp}.csv`);
    }
  }

  downloadFile(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

// Initialize the application
document.addEventListener("DOMContentLoaded", () => {
  new StressTestingTool();
});
