#!/usr/bin/env node

/**
 * Unified Test Runner for Pake CLI
 *
 * This is a simplified, unified test runner that replaces the scattered
 * test files with a single, easy-to-use interface.
 */

import { execSync, spawn } from "child_process";
import fs from "fs";
import path from "path";
import ora from "ora";
import config, { TIMEOUTS, TEST_URLS } from "./config.js";

class PakeTestRunner {
  constructor() {
    this.results = [];
    this.tempFiles = [];
    this.tempDirs = [];
  }

  async runAll(options = {}) {
    const {
      unit = true,
      integration = true,
      builder = true,
      pakeCliTests = false,
      e2e = false,
      quick = false,
      realBuild = false, // Add option for real build test
    } = options;

    console.log("Pake CLI Test Suite");
    console.log("======================\n");

    this.validateEnvironment();

    // Clean up any leftover files from previous test runs
    console.log("[Clean] Removing any leftover test artifacts...");
    this.cleanupTempIcons();

    let testCount = 0;

    if (unit && !quick) {
      console.log("Running CLI Health Checks...");
      await this.runCliHealthChecks();
      testCount++;

      console.log("\nRunning Project Unit Tests (Vitest)...");
      try {
        execSync("npx vitest run", {
          stdio: "inherit",
          cwd: config.PROJECT_ROOT,
        });
        this.results.push({ name: "Vitest Unit Tests", passed: true });
        testCount++;
      } catch (e) {
        console.log("[FAIL] Vitest unit tests failed");
        this.results.push({
          name: "Vitest Unit Tests",
          passed: false,
          error: e.message,
        });
      }
    }

    if (integration && !quick) {
      console.log("\n[Integration] Running Integration Tests...");
      await this.runIntegrationTests();
      testCount++;
    }

    if (builder && !quick) {
      console.log("\n[Build] Running Builder Tests...");
      await this.runBuilderTests();
      testCount++;
    }

    if (pakeCliTests) {
      console.log("\n[Package] Running Pake-CLI GitHub Actions Tests...");
      await this.runPakeCliTests();
      testCount++;
    }

    if (e2e && !quick) {
      console.log("\n[Run] Running End-to-End Tests...");
      await this.runE2ETests();
      testCount++;

      console.log("\n[Network] Running Proxy Configuration Test...");
      await this.runProxyTest();
      testCount++;
    }

    if (builder && !quick) {
      console.log("\n[Build] Running Local File Build Test...");
      await this.runLocalFileTest();
      testCount++;
    }

    if (realBuild && !quick) {
      // On macOS, prefer multi-arch test as it's more likely to catch issues
      if (process.platform === "darwin") {
        console.log("\n[Build] Running Real Build Test (Multi-Arch)...");
        await this.runMultiArchBuildTest();
        testCount++;
      } else {
        console.log("\n[Build] Running Real Build Test...");
        await this.runRealBuildTest();
        testCount++;
      }
    }

    this.cleanup();
    this.displayFinalResults();

    const passed = this.results.filter((r) => r.passed).length;
    const total = this.results.length;

    return passed === total;
  }

  validateEnvironment() {
    console.log("Environment Validation:");
    console.log("-----------------------");

    // Check if CLI file exists
    if (!fs.existsSync(config.CLI_PATH)) {
      console.log("[FAIL] CLI file not found. Run: pnpm run cli:build");
      process.exit(1);
    }
    console.log("[PASS] CLI file exists");

    // Check if CLI is executable
    try {
      execSync(`node "${config.CLI_PATH}" --version`, {
        encoding: "utf8",
        timeout: TIMEOUTS.QUICK,
      });
      console.log("[PASS] CLI responds");
    } catch (error) {
      const reason =
        error.signal === "SIGTERM"
          ? `timed out after ${TIMEOUTS.QUICK}ms`
          : error.message;
      console.log(`[FAIL] CLI did not respond: ${reason}`);
      process.exit(1);
    }

    // Platform info
    console.log(`[PASS] Platform: ${process.platform} (${process.arch})`);
    console.log(`[PASS] Node.js: ${process.version}`);

    const isCI = process.env.CI || process.env.GITHUB_ACTIONS;
    console.log(`[INFO] CI Environment: ${isCI ? "Yes" : "No"}`);

    console.log();
  }

  async runTest(name, testFn, timeout = TIMEOUTS.MEDIUM) {
    const spinner = ora(`Running ${name}...`).start();

    try {
      const result = await Promise.race([
        testFn(),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error("Test timeout")), timeout),
        ),
      ]);

      if (result) {
        spinner.succeed(`${name}: PASS`);
        this.results.push({ name, passed: true });
      } else {
        spinner.fail(`${name}: FAIL`);
        this.results.push({ name, passed: false });
      }
    } catch (error) {
      spinner.fail(`${name}: ERROR - ${error.message.slice(0, 100)}...`);
      this.results.push({
        name,
        passed: false,
        error: error.message,
      });
    }
  }

  async runCliHealthChecks() {
    // Version command test
    await this.runTest(
      "Version Command",
      () => {
        const output = execSync(`node "${config.CLI_PATH}" --version`, {
          encoding: "utf8",
          timeout: TIMEOUTS.QUICK,
        });
        return /^\d+\.\d+\.\d+/.test(output.trim());
      },
      TIMEOUTS.QUICK,
    );

    // Help command test
    await this.runTest(
      "Help Command",
      () => {
        const output = execSync(`node "${config.CLI_PATH}"`, {
          encoding: "utf8",
          timeout: TIMEOUTS.QUICK,
        });
        return output.includes("Usage: cli [url] [options]");
      },
      TIMEOUTS.QUICK,
    );

    // URL validation test
    await this.runTest("URL Validation", () => {
      try {
        execSync(`node "${config.CLI_PATH}" "invalid-url" --name TestApp`, {
          encoding: "utf8",
          timeout: TIMEOUTS.QUICK,
        });
        return false; // Should have failed
      } catch (error) {
        return error.status !== 0;
      }
    });

    // Number validation test
    await this.runTest("Number Validation", () => {
      try {
        execSync(`node "${config.CLI_PATH}" https://example.com --width abc`, {
          encoding: "utf8",
          timeout: TIMEOUTS.QUICK,
        });
        return false; // Should throw error
      } catch (error) {
        return error.message.includes("Not a number");
      }
    });

    // CLI response time test
    await this.runTest("CLI Response Time", () => {
      const start = Date.now();
      execSync(`node "${config.CLI_PATH}" --version`, {
        encoding: "utf8",
        timeout: TIMEOUTS.QUICK,
      });
      const elapsed = Date.now() - start;
      return elapsed < 5000;
    });

    // Weekly URL accessibility test
    await this.runTest("Weekly URL Accessibility", () => {
      try {
        const testCommand = `node "${config.CLI_PATH}" ${TEST_URLS.WEEKLY} --name "URLTest" --debug`;
        execSync(`echo "n" | timeout 5s ${testCommand} || true`, {
          encoding: "utf8",
          timeout: 8000,
        });
        return true; // If we get here, URL was parsed successfully
      } catch (error) {
        return (
          !error.message.includes("Invalid URL") &&
          !error.message.includes("invalid")
        );
      }
    });
  }

  async runIntegrationTests() {
    // Process spawning test
    await this.runTest("CLI Process Spawning", () => {
      return new Promise((resolve) => {
        const child = spawn("node", [config.CLI_PATH, "--version"], {
          stdio: ["pipe", "pipe", "pipe"],
        });

        let output = "";
        child.stdout.on("data", (data) => {
          output += data.toString();
        });

        child.on("close", (code) => {
          resolve(code === 0 && /\d+\.\d+\.\d+/.test(output));
        });

        setTimeout(() => {
          child.kill();
          resolve(false);
        }, TIMEOUTS.QUICK);
      });
    });

    // File system permissions test
    await this.runTest("File System Permissions", () => {
      try {
        const testFile = "test-write-permission.tmp";
        fs.writeFileSync(testFile, "test");
        this.trackTempFile(testFile);

        const cliStats = fs.statSync(config.CLI_PATH);
        return cliStats.isFile();
      } catch {
        return false;
      }
    });

    // Dependency resolution test
    await this.runTest("Dependency Resolution", () => {
      try {
        const packageJsonPath = path.join(config.PROJECT_ROOT, "package.json");
        const packageJson = JSON.parse(
          fs.readFileSync(packageJsonPath, "utf8"),
        );

        const essentialDeps = ["commander", "chalk", "fs-extra", "execa"];
        return essentialDeps.every(
          (dep) => packageJson.dependencies && packageJson.dependencies[dep],
        );
      } catch {
        return false;
      }
    });
  }

  async runBuilderTests() {
    // Platform detection test
    await this.runTest("Platform Detection", () => {
      const platform = process.platform;
      const platformConfigs = {
        darwin: { ext: ".dmg", multiArch: true },
        win32: { ext: ".msi", multiArch: false },
        linux: { ext: ".deb", multiArch: false },
      };

      const config = platformConfigs[platform];
      return config && typeof config.ext === "string";
    });

    // Architecture detection test
    await this.runTest("Architecture Detection", () => {
      const currentArch = process.arch;
      const macArch = currentArch === "arm64" ? "aarch64" : currentArch;
      const linuxArch = currentArch === "x64" ? "amd64" : currentArch;

      return typeof macArch === "string" && typeof linuxArch === "string";
    });

    // File naming pattern test
    await this.runTest("File Naming Patterns", () => {
      const testNames = ["Simple App", "App-With_Symbols", "CamelCaseApp"];
      return testNames.every((name) => {
        const processed = name.toLowerCase().replace(/\s+/g, "");
        return processed.length > 0;
      });
    });
  }

  async runPakeCliTests() {
    // Package installation test
    await this.runTest(
      "pake-cli Package Installation",
      async () => {
        try {
          execSync("pnpm install pake-cli@latest", {
            encoding: "utf8",
            timeout: 60000,
            cwd: "/tmp",
          });

          const pakeCliPath = "/tmp/node_modules/.bin/pake";
          return fs.existsSync(pakeCliPath);
        } catch (error) {
          console.error("Package installation failed:", error.message);
          return false;
        }
      },
      TIMEOUTS.LONG,
    );

    // Version command test
    await this.runTest("pake-cli Version Command", async () => {
      try {
        const version = execSync("npx pake --version", {
          encoding: "utf8",
          timeout: 10000,
        });
        return /^\d+\.\d+\.\d+/.test(version.trim());
      } catch {
        return false;
      }
    });

    // Configuration validation test
    await this.runTest("Configuration Validation", async () => {
      try {
        const validateConfig = (config) => {
          const required = ["url", "name", "width", "height"];
          const hasRequired = required.every((field) =>
            config.hasOwnProperty(field),
          );

          const validTypes =
            typeof config.url === "string" &&
            typeof config.name === "string" &&
            typeof config.width === "number" &&
            typeof config.height === "number";

          let validUrl = false;
          try {
            new URL(config.url);
            validUrl = true;
          } catch {}

          const validName = config.name.length > 0;
          return hasRequired && validTypes && validUrl && validName;
        };

        const testConfig = {
          url: "https://github.com",
          name: "github",
          width: 1200,
          height: 780,
        };

        return validateConfig(testConfig);
      } catch {
        return false;
      }
    });
  }

  async runE2ETests() {
    // GitHub.com CLI build test
    await this.runTest(
      "GitHub.com CLI Build Test",
      async () => {
        return new Promise((resolve, reject) => {
          const testName = "GitHubApp";
          const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --debug --width 1200 --height 780`;

          const child = spawn(command, {
            shell: true,
            cwd: config.PROJECT_ROOT,
            stdio: ["pipe", "pipe", "pipe"],
            env: {
              ...process.env,
              PAKE_E2E_TEST: "1",
              PAKE_CREATE_APP: "1",
            },
          });

          let buildStarted = false;
          let configGenerated = false;

          child.stdout.on("data", (data) => {
            const output = data.toString();
            if (
              output.includes("Building app") ||
              output.includes("Compiling") ||
              output.includes("Installing package") ||
              output.includes("Bundling")
            ) {
              buildStarted = true;
            }
            if (
              output.includes("GitHub") &&
              (output.includes("config") || output.includes("name"))
            ) {
              configGenerated = true;
            }
          });

          child.stderr.on("data", (data) => {
            const output = data.toString();
            if (
              output.includes("Building app") ||
              output.includes("Compiling") ||
              output.includes("Installing package") ||
              output.includes("Bundling") ||
              output.includes("Finished") ||
              output.includes("Built application at:")
            ) {
              buildStarted = true;
            }
          });

          // Kill process after 60 seconds if build started
          const timeout = setTimeout(() => {
            child.kill("SIGTERM");

            const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`);
            const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`);
            this.trackTempFile(appFile);
            this.trackTempFile(dmgFile);

            if (buildStarted) {
              console.log(
                `✓ GitHub.com CLI build started successfully (${testName})`,
              );
              resolve(true);
            } else {
              reject(
                new Error("GitHub.com CLI build did not start within timeout"),
              );
            }
          }, 60000);

          child.on("close", () => {
            clearTimeout(timeout);
            const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`);
            const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`);
            this.trackTempFile(appFile);
            this.trackTempFile(dmgFile);

            if (buildStarted) {
              resolve(true);
            } else {
              reject(
                new Error("GitHub.com CLI build process ended before starting"),
              );
            }
          });

          child.on("error", (error) => {
            reject(
              new Error(`GitHub.com CLI build process error: ${error.message}`),
            );
          });

          child.stdin.end();
        });
      },
      70000, // 70 seconds timeout
    );

    // Configuration verification test
    await this.runTest(
      "Configuration File Verification",
      async () => {
        const pakeDir = path.join(config.PROJECT_ROOT, "src-tauri", ".pake");

        return new Promise((resolve, reject) => {
          const testName = "GitHubConfigTest";
          const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --debug --width 1200 --height 780`;

          const child = spawn(command, {
            shell: true,
            cwd: config.PROJECT_ROOT,
            stdio: ["pipe", "pipe", "pipe"],
            env: {
              ...process.env,
              PAKE_E2E_TEST: "1",
              PAKE_CREATE_APP: "1",
            },
          });

          const checkConfigFiles = () => {
            if (fs.existsSync(pakeDir)) {
              const configFile = path.join(pakeDir, "tauri.conf.json");
              const pakeConfigFile = path.join(pakeDir, "pake.json");

              if (fs.existsSync(configFile) && fs.existsSync(pakeConfigFile)) {
                try {
                  const config = JSON.parse(
                    fs.readFileSync(configFile, "utf8"),
                  );
                  const pakeConfig = JSON.parse(
                    fs.readFileSync(pakeConfigFile, "utf8"),
                  );

                  if (
                    config.productName === testName &&
                    pakeConfig.windows[0].url === "https://github.com/"
                  ) {
                    child.kill("SIGTERM");
                    this.trackTempDir(pakeDir);
                    console.log(
                      "✓ GitHub.com configuration files verified correctly",
                    );
                    resolve(true);
                    return true;
                  }
                } catch (error) {
                  // Continue if config parsing fails
                }
              }
            }
            return false;
          };

          child.stdout.on("data", (data) => {
            const output = data.toString();
            if (
              output.includes("Installing package") ||
              output.includes("Building app")
            ) {
              setTimeout(checkConfigFiles, 1000);
            }
          });

          child.stderr.on("data", (data) => {
            const output = data.toString();
            if (
              output.includes("Installing package") ||
              output.includes("Building app") ||
              output.includes("Package installed")
            ) {
              setTimeout(checkConfigFiles, 1000);
            }
          });

          // Timeout after 20 seconds
          setTimeout(() => {
            child.kill("SIGTERM");
            this.trackTempDir(pakeDir);
            reject(new Error("GitHub.com configuration verification timeout"));
          }, 40000);

          child.on("error", (error) => {
            reject(
              new Error(
                `GitHub.com config verification error: ${error.message}`,
              ),
            );
          });

          child.stdin.end();
        });
      },
      45000,
    );
  }

  async runProxyTest() {
    await this.runTest("Proxy Configuration", async () => {
      const command = `node "${config.CLI_PATH}" "https://google.com" --name "ProxyTest" --proxy-url "http://127.0.0.1:7890" --debug`;
      // We just want to check if the command parses the proxy argument correctly
      // It might fail to connect if no proxy is running, but that's expected
      try {
        execSync(`echo "n" | timeout 5s ${command} || true`, {
          encoding: "utf8",
          timeout: 8000,
        });
        return true;
      } catch (error) {
        // If it fails with "connection refused" or similar, it means it TRIED to use the proxy
        return true;
      }
    });
  }

  async runLocalFileTest() {
    await this.runTest("Local File Build Handling", async () => {
      const testFile = path.join(config.PROJECT_ROOT, "test-local.html");
      fs.writeFileSync(
        testFile,
        "<html><body><h1>Hello Pake</h1></body></html>",
      );
      this.trackTempFile(testFile);

      try {
        const command = `node "${config.CLI_PATH}" "${testFile}" --name "LocalApp" --debug`;
        // We just verify it accepts the local file path
        execSync(`echo "n" | timeout 5s ${command} || true`, {
          encoding: "utf8",
          timeout: 8000,
        });
        return true;
      } catch (error) {
        // Validation failure is what we want to catch (if it rejected local files)
        return !error.message.includes("Invalid URL");
      }
    });
  }

  async runRealBuildTest() {
    // Real build test that actually creates a complete app
    await this.runTest(
      "Complete GitHub.com App Build",
      async () => {
        return new Promise((resolve, reject) => {
          const testName = "GitHubRealBuild";
          // Platform-specific output files
          const outputFiles = {
            darwin: {
              app: path.join(config.PROJECT_ROOT, `${testName}.app`),
              installer: path.join(config.PROJECT_ROOT, `${testName}.dmg`),
              bundleDir: path.join(
                config.PROJECT_ROOT,
                "src-tauri/target/release/bundle",
              ),
            },
            linux: {
              app: path.join(
                config.PROJECT_ROOT,
                `src-tauri/target/release/pake`,
              ),
              installer: path.join(
                config.PROJECT_ROOT,
                "src-tauri/target/release/bundle/deb",
              ),
              bundleDir: path.join(
                config.PROJECT_ROOT,
                "src-tauri/target/release/bundle",
              ),
            },
            win32: {
              app: path.join(
                config.PROJECT_ROOT,
                "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi",
              ),
              installer: path.join(
                config.PROJECT_ROOT,
                "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi",
              ),
              bundleDir: path.join(
                config.PROJECT_ROOT,
                "src-tauri/target/x86_64-pc-windows-msvc/release/bundle",
              ),
              // Alternative directories to check
              altDirs: [
                path.join(
                  config.PROJECT_ROOT,
                  "src-tauri/target/release/bundle/msi",
                ),
                path.join(
                  config.PROJECT_ROOT,
                  "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis",
                ),
                path.join(
                  config.PROJECT_ROOT,
                  "src-tauri/target/release/bundle/nsis",
                ),
              ],
            },
          };
          const platform = process.platform;
          const expectedFiles = outputFiles[platform] || outputFiles.darwin;

          console.log(
            `[Integration] Starting real build test for GitHub.com...`,
          );
          console.log(`[Note] Platform: ${platform}`);
          console.log(`[Note] Expected app directory: ${expectedFiles.app}`);
          console.log(
            `[Note] Expected installer directory: ${expectedFiles.installer}`,
          );
          if (expectedFiles.bundleDir) {
            console.log(`[Note] Bundle directory: ${expectedFiles.bundleDir}`);
          }
          if (expectedFiles.altDirs) {
            console.log(`[Note] Alternative directories to check:`);
            expectedFiles.altDirs.forEach((dir, i) => {
              console.log(`     ${i + 1}. ${dir}`);
            });
          }

          const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --width 1200 --height 800 --hide-title-bar`;

          const child = spawn(command, {
            shell: true,
            cwd: config.PROJECT_ROOT,
            stdio: ["pipe", "pipe", "pipe"],
            env: {
              ...process.env,
              PAKE_CREATE_APP: "1",
            },
          });

          let buildStarted = false;
          let compilationStarted = false;

          // Track progress without too much noise
          child.stdout.on("data", (data) => {
            const output = data.toString();
            if (output.includes("Installing package")) {
              console.log("   [Package] Installing dependencies...");
            }
            if (output.includes("Building app")) {
              buildStarted = true;
              console.log("   [Build]  Build started...");
            }
            if (output.includes("Compiling")) {
              compilationStarted = true;
              console.log("   ⚙️  Compiling...");
            }
            if (output.includes("Bundling")) {
              console.log("   [Package] Bundling...");
            }
            if (output.includes("Built application at:")) {
              console.log("   [PASS] Build completed!");
            }
          });

          let errorOutput = "";
          child.stderr.on("data", (data) => {
            const output = data.toString();
            if (output.includes("Building app")) buildStarted = true;
            if (output.includes("Compiling")) compilationStarted = true;
            if (output.includes("Finished"))
              console.log("   [PASS] Compilation finished!");

            // Capture error output for debugging
            if (
              output.includes("error:") ||
              output.includes("Error:") ||
              output.includes("ERROR")
            ) {
              errorOutput += output;
            }
          });

          // Real timeout - 8 minutes for actual build
          const timeout = setTimeout(() => {
            console.log(
              "   [Check] Build timeout reached, checking for output files...",
            );

            const foundFiles = this.findBuildOutputFiles(testName, platform);

            if (foundFiles.length > 0) {
              console.log(
                "   [Success] Build completed successfully - found output files!",
              );
              foundFiles.forEach((file) => {
                console.log(`   [App] Found: ${file.path} (${file.type})`);
              });
              console.log("   [Success] Build artifacts tracked for cleanup");
              child.kill("SIGTERM");
              resolve(true);
            } else {
              console.log(
                "   [Warn]  Build process completed but no output files found",
              );
              this.debugBuildDirectories();
              child.kill("SIGTERM");
              reject(
                new Error("Real build test timeout - no output files found"),
              );
            }
          }, 480000); // 8 minutes

          child.on("close", (code) => {
            clearTimeout(timeout);

            console.log(
              `   [Status] Build process finished with exit code: ${code}`,
            );

            const foundFiles = this.findBuildOutputFiles(testName, platform);

            if (foundFiles.length > 0) {
              console.log(
                "   [Success] Real build test SUCCESS: Build file(s) generated!",
              );
              foundFiles.forEach((file) => {
                console.log(`   [App] ${file.type}: ${file.path}`);
                try {
                  const stats = fs.statSync(file.path);
                  const size = (stats.size / 1024 / 1024).toFixed(1);
                  console.log(`      Size: ${size}MB`);
                } catch (error) {
                  console.log(`      (Could not get file size)`);
                }
              });
              console.log("   [Success] Build artifacts tracked for cleanup");
              // Track files for cleanup
              foundFiles.forEach((f) => this.trackTempFile(f.path));
              resolve(true);
            } else if (code === 0 && buildStarted && compilationStarted) {
              console.log(
                "   [Warn] Build process completed but no output files found",
              );
              this.debugBuildDirectories();
              resolve(false);
            } else {
              console.log(
                `   [FAIL] Build process failed with exit code: ${code}`,
              );
              if (buildStarted) {
                console.log(
                  "   [Status] Build was started but failed during execution",
                );
                if (errorOutput.trim()) {
                  console.log("   [Check] Error details:");
                  errorOutput.split("\n").forEach((line) => {
                    if (line.trim()) console.log(`     ${line.trim()}`);
                  });
                }
                this.debugBuildDirectories();
              } else {
                console.log(
                  "   [Status] Build failed before starting compilation",
                );
                if (errorOutput.trim()) {
                  console.log("   [Check] Error details:");
                  errorOutput.split("\n").forEach((line) => {
                    if (line.trim()) console.log(`     ${line.trim()}`);
                  });
                }
              }
              reject(new Error(`Real build test failed with code ${code}`));
            }
          });

          child.on("error", (error) => {
            clearTimeout(timeout);
            reject(
              new Error(`Real build test process error: ${error.message}`),
            );
          });

          child.stdin.end();
        });
      },
      500000, // 8+ minutes timeout
    );
  }

  async runMultiArchBuildTest() {
    // Multi-arch build test specifically for macOS
    await this.runTest(
      "Multi-Arch GitHub.com Build (Universal Binary)",
      async () => {
        return new Promise((resolve, reject) => {
          const testName = "GitHubMultiArch";
          const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`);
          const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`);

          console.log(
            `[Integration] Starting multi-arch build test for GitHub.com...`,
          );
          console.log(`[Note] Expected output: ${appFile}`);
          console.log(
            `[Build]  Building Universal Binary (Intel + Apple Silicon)`,
          );

          const command = `node "${config.CLI_PATH}" "https://github.com" --name "${testName}" --width 1200 --height 800 --hide-title-bar --multi-arch`;

          const child = spawn(command, {
            shell: true,
            cwd: config.PROJECT_ROOT,
            stdio: ["pipe", "pipe", "pipe"],
            env: {
              ...process.env,
              PAKE_CREATE_APP: "1",
              HDIUTIL_QUIET: "1",
              HDIUTIL_NO_AUTOOPEN: "1",
            },
          });

          let buildStarted = false;
          let compilationStarted = false;

          // Track progress
          child.stdout.on("data", (data) => {
            const output = data.toString();
            if (output.includes("Installing package")) {
              console.log("   [Package] Installing dependencies...");
            }
            if (output.includes("Building app")) {
              buildStarted = true;
              console.log("   [Build]  Multi-arch build started...");
            }
            if (output.includes("Compiling")) {
              compilationStarted = true;
              console.log("   ⚙️  Compiling for multiple architectures...");
            }
            if (
              output.includes("universal-apple-darwin") ||
              output.includes("Universal")
            ) {
              console.log("   [Multi] Universal binary target detected");
            }
            if (output.includes("Bundling")) {
              console.log("   [Package] Bundling universal binary...");
            }
            if (output.includes("Built application at:")) {
              console.log("   [PASS] Multi-arch build completed!");
            }
          });

          child.stderr.on("data", (data) => {
            const output = data.toString();
            if (output.includes("Building app")) buildStarted = true;
            if (output.includes("Compiling")) compilationStarted = true;
            if (output.includes("Finished"))
              console.log("   [PASS] Multi-arch compilation finished!");
          });

          // Multi-arch builds take longer - 20 minutes timeout
          const timeout = setTimeout(() => {
            console.log(
              "   [Check] Multi-arch build timeout reached, checking for output files...",
            );

            const foundFiles = this.findBuildOutputFiles(testName, "darwin");

            if (foundFiles.length > 0) {
              console.log(
                "   [Success] Multi-arch build completed successfully!",
              );
              foundFiles.forEach((file) => {
                console.log(`   [App] Found: ${file.path} (${file.type})`);
              });
              console.log(
                "   [Multi] Universal binary preserved for inspection",
              );
              child.kill("SIGTERM");
              resolve(true);
            } else {
              console.log(
                "   [FAIL] Multi-arch build timeout - no output files generated",
              );
              this.debugBuildDirectories(
                {
                  app: appFile,
                  installer: dmgFile,
                  bundleDir: path.join(
                    config.PROJECT_ROOT,
                    "src-tauri/target/universal-apple-darwin/release/bundle",
                  ),
                },
                "darwin",
              );
              child.kill("SIGTERM");
              reject(new Error("Multi-arch build test timeout"));
            }
          }, 1200000); // 20 minutes for multi-arch

          child.on("close", (code) => {
            clearTimeout(timeout);

            console.log(
              `   [Status] Multi-arch build process finished with exit code: ${code}`,
            );

            const foundFiles = this.findBuildOutputFiles(testName, "darwin");

            if (foundFiles.length > 0) {
              console.log(
                "   [Success] Multi-arch build test SUCCESS: Universal binary generated!",
              );
              foundFiles.forEach((file) => {
                console.log(`   [App] ${file.type}: ${file.path}`);
              });
              console.log(
                "   [Multi] Universal binary preserved for inspection",
              );

              // Verify it's actually a universal binary
              const appFile = foundFiles.find((f) => f.type.includes("App"));
              if (appFile) {
                try {
                  const binaryPath = path.join(
                    appFile.path,
                    "Contents/MacOS/pake",
                  );
                  const fileOutput = execSync(`file "${binaryPath}"`, {
                    encoding: "utf8",
                  });
                  if (fileOutput.includes("universal binary")) {
                    console.log(
                      "   [PASS] Verified: Universal binary created successfully",
                    );
                  } else {
                    console.log(
                      "   [Warn]  Note: Binary architecture:",
                      fileOutput.trim(),
                    );
                  }
                } catch (error) {
                  console.log(
                    "   [Warn]  Could not verify binary architecture",
                  );
                }
              }

              resolve(true);
            } else if (buildStarted && compilationStarted) {
              // If build started and compilation happened, but no output files found
              console.log(
                "   [Warn]  Multi-arch build process completed but no output files found",
              );
              this.debugBuildDirectories(
                {
                  app: appFile,
                  installer: dmgFile,
                  bundleDir: path.join(
                    config.PROJECT_ROOT,
                    "src-tauri/target/universal-apple-darwin/release/bundle",
                  ),
                },
                "darwin",
              );
              resolve(false);
            } else {
              // Only reject if the build never started or failed early
              reject(
                new Error(`Multi-arch build test failed with code ${code}`),
              );
            }
          });

          child.on("error", (error) => {
            clearTimeout(timeout);
            reject(
              new Error(
                `Multi-arch build test process error: ${error.message}`,
              ),
            );
          });

          child.stdin.end();
        });
      },
      1250000, // 20+ minutes timeout
    );
  }

  // Simplified build output detection - if build succeeds, check for any output files
  findBuildOutputFiles(testName, platform) {
    const foundFiles = [];
    console.log(`   [Check] Checking for ${platform} build outputs...`);

    // Simple approach: look for common build artifacts in project root and common locations
    const searchLocations = [
      // Always check project root first (most builds output there)
      config.PROJECT_ROOT,
      // Platform-specific bundle directories
      ...(platform === "linux"
        ? [
            path.join(config.PROJECT_ROOT, "src-tauri/target/release"),
            path.join(
              config.PROJECT_ROOT,
              "src-tauri/target/release/bundle/deb",
            ),
          ]
        : []),
      ...(platform === "win32"
        ? [
            path.join(
              config.PROJECT_ROOT,
              "src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi",
            ),
            path.join(
              config.PROJECT_ROOT,
              "src-tauri/target/release/bundle/msi",
            ),
          ]
        : []),
      ...(platform === "darwin"
        ? [
            path.join(
              config.PROJECT_ROOT,
              "src-tauri/target/release/bundle/macos",
            ),
            path.join(
              config.PROJECT_ROOT,
              "src-tauri/target/release/bundle/dmg",
            ),
            path.join(
              config.PROJECT_ROOT,
              "src-tauri/target/universal-apple-darwin/release/bundle",
            ),
          ]
        : []),
    ];

    // Define what we're looking for based on platform
    const buildPatterns = {
      win32: [".msi", ".exe"],
      linux: [".deb", ".appimage"],
      darwin: [".dmg", ".app"],
    };

    const patterns = buildPatterns[platform] || buildPatterns.darwin;

    for (const location of searchLocations) {
      if (!fs.existsSync(location)) {
        continue;
      }

      console.log(
        `      [Dir] Checking: ${path.relative(config.PROJECT_ROOT, location)}`,
      );

      try {
        const items = fs.readdirSync(location);
        const buildFiles = items.filter((item) => {
          const itemPath = path.join(location, item);
          const stats = fs.statSync(itemPath);

          // Skip common non-build directories
          if (
            stats.isDirectory() &&
            [".git", ".github", "node_modules", "src", "bin", "tests"].includes(
              item,
            )
          ) {
            return false;
          }

          // Check if it's a build artifact we care about
          const lowerItem = item.toLowerCase();
          return (
            patterns.some((pattern) => lowerItem.endsWith(pattern)) ||
            lowerItem.includes(testName.toLowerCase()) ||
            (lowerItem.includes("github") && !item.startsWith(".")) || // Avoid .github directory
            (platform === "linux" && item === "pake")
          ); // Linux binary
        });

        buildFiles.forEach((file) => {
          const fullPath = path.join(location, file);
          const stats = fs.statSync(fullPath);

          let fileType = "Build Artifact";
          if (file.endsWith(".msi")) fileType = "MSI Installer";
          else if (file.endsWith(".exe")) fileType = "Windows Executable";
          else if (file.endsWith(".deb")) fileType = "DEB Package";
          else if (file.endsWith(".appimage")) fileType = "AppImage";
          else if (file.endsWith(".dmg")) fileType = "DMG Image";
          else if (file.endsWith(".app"))
            fileType = stats.isDirectory() ? "macOS App Bundle" : "macOS App";
          else if (file === "pake") fileType = "Linux Binary";

          foundFiles.push({
            path: fullPath,
            type: fileType,
            size: stats.isFile() ? stats.size : 0,
          });

          const size =
            stats.isFile() && stats.size > 0
              ? ` (${(stats.size / 1024 / 1024).toFixed(1)}MB)`
              : "";
          console.log(`      [PASS] Found ${fileType}: ${file}${size}`);
        });

        // For Linux, also check inside architecture directories
        if (platform === "linux") {
          const archDirs = items.filter(
            (item) => item.includes("amd64") || item.includes("x86_64"),
          );

          for (const archDir of archDirs) {
            const archPath = path.join(location, archDir);
            if (fs.statSync(archPath).isDirectory()) {
              console.log(`      [Check] Checking arch directory: ${archDir}`);
              try {
                const archFiles = fs.readdirSync(archPath);
                archFiles
                  .filter((f) => f.endsWith(".deb"))
                  .forEach((debFile) => {
                    const debPath = path.join(archPath, debFile);
                    const debStats = fs.statSync(debPath);
                    foundFiles.push({
                      path: debPath,
                      type: "DEB Package",
                      size: debStats.size,
                    });
                    const size = `(${(debStats.size / 1024 / 1024).toFixed(1)}MB)`;
                    console.log(
                      `      [PASS] Found DEB Package: ${debFile} ${size}`,
                    );
                  });
              } catch (error) {
                console.log(
                  `      [Warn]  Could not check ${archDir}: ${error.message}`,
                );
              }
            }
          }
        }
      } catch (error) {
        console.log(
          `      [Warn]  Could not read ${location}: ${error.message}`,
        );
      }
    }

    console.log(`   [Status] Found ${foundFiles.length} build artifact(s)`);
    return foundFiles;
  }

  // Debug function to show directory structure
  debugBuildDirectories() {
    console.log("   [Check] Debug: Analyzing build directories...");

    const targetDir = path.join(config.PROJECT_ROOT, "src-tauri/target");
    if (fs.existsSync(targetDir)) {
      console.log("   [Check] Target directory structure:");
      try {
        this.listTargetContents(targetDir);
      } catch (error) {
        console.log(
          `   [Warn]  Could not list target contents: ${error.message}`,
        );
      }
    } else {
      console.log(`   [FAIL] Target directory does not exist: ${targetDir}`);
    }

    // Check project root for direct outputs
    console.log("   [Check] Project root files:");
    try {
      const rootFiles = fs
        .readdirSync(config.PROJECT_ROOT)
        .filter(
          (file) =>
            file.endsWith(".app") ||
            file.endsWith(".dmg") ||
            file.endsWith(".msi") ||
            file.endsWith(".deb") ||
            file.endsWith(".exe"),
        );
      if (rootFiles.length > 0) {
        rootFiles.forEach((file) => {
          console.log(`      [App] ${file}`);
        });
      } else {
        console.log(`      (No build artifacts in project root)`);
      }
    } catch (error) {
      console.log(`      [FAIL] Error reading project root: ${error.message}`);
    }
  }

  listTargetContents(targetDir, maxDepth = 3, currentDepth = 0) {
    if (currentDepth >= maxDepth) return;

    try {
      const items = fs.readdirSync(targetDir);
      items.forEach((item) => {
        const fullPath = path.join(targetDir, item);
        const relativePath = path.relative(config.PROJECT_ROOT, fullPath);
        const indent = "     ".repeat(currentDepth + 1);

        try {
          const stats = fs.statSync(fullPath);
          if (stats.isDirectory()) {
            console.log(`${indent}[Dir] ${relativePath}/`);
            // Show more directories for Windows debugging
            if (
              item === "bundle" ||
              item === "release" ||
              item === "msi" ||
              item === "nsis" ||
              item.includes("windows") ||
              item.includes("msvc")
            ) {
              this.listTargetContents(fullPath, maxDepth, currentDepth + 1);
            }
          } else {
            const size =
              stats.size > 0
                ? ` (${(stats.size / 1024 / 1024).toFixed(1)}MB)`
                : "";
            console.log(`${indent}[File] ${relativePath}${size}`);
          }
        } catch (statError) {
          console.log(`${indent}❓ ${relativePath} (cannot stat)`);
        }
      });
    } catch (error) {
      console.log(
        `     [Warn]  Could not list contents of ${targetDir}: ${error.message}`,
      );
    }
  }

  trackTempFile(filepath) {
    this.tempFiles.push(filepath);
  }

  trackTempDir(dirpath) {
    this.tempDirs.push(dirpath);
  }

  cleanupTempIcons() {
    // Clean up temporary icon files generated during tests
    const iconsDir = path.join(config.PROJECT_ROOT, "src-tauri/icons");
    const testNames = [
      "urltest",
      "testapp",
      "githubapp",
      "githubmultiarch",
      "githubconfigtest",
      "localapp",
      "proxytest",
    ];

    testNames.forEach((name) => {
      const iconPath = path.join(iconsDir, `${name}.icns`);
      try {
        if (fs.existsSync(iconPath)) {
          fs.unlinkSync(iconPath);
          console.log(`   [Clean] Cleaned up temporary icon: ${name}.icns`);
        }
      } catch (error) {
        console.warn(`Warning: Could not clean up icon ${iconPath}`);
      }
    });
  }

  cleanup() {
    console.log("\nCleaning up test artifacts...");

    // Clean up temporary icon files generated during tests
    this.cleanupTempIcons();

    // Clean up tracked files
    this.tempFiles.forEach((file) => {
      try {
        if (fs.existsSync(file)) {
          if (fs.statSync(file).isDirectory()) {
            fs.rmSync(file, { recursive: true, force: true });
          } else {
            fs.unlinkSync(file);
          }
        }
      } catch (error) {
        // Ignore errors during cleanup
      }
    });

    this.tempDirs.forEach((dir) => {
      try {
        if (fs.existsSync(dir)) {
          fs.rmSync(dir, { recursive: true, force: true });
        }
      } catch (error) {
        // Ignore errors
      }
    });

    // Aggressive cleanup of known test artifacts in project root
    const testPatterns = [
      "GitHubRealBuild",
      "GitHubApp",
      "GitHubMultiArch",
      "GitHubConfigTest",
      "LocalApp",
      "ProxyTest",
      "URLTest",
    ];

    const extensions = [".app", ".dmg", ".msi", ".deb", ".exe", ".AppImage"];

    try {
      const files = fs.readdirSync(config.PROJECT_ROOT);
      files.forEach((file) => {
        // Check if file matches any test name pattern and extension
        const isTestArtifact =
          testPatterns.some((pattern) => file.includes(pattern)) &&
          (extensions.some((ext) => file.endsWith(ext)) ||
            (!file.includes(".") &&
              !fs
                .statSync(path.join(config.PROJECT_ROOT, file))
                .isDirectory())); // Linux binary often has no extension

        if (isTestArtifact) {
          const fullPath = path.join(config.PROJECT_ROOT, file);
          console.log(`   [Clean] Removing artifact: ${file}`);
          fs.rmSync(fullPath, { recursive: true, force: true });
        }
      });

      // Also clean src-tauri/.pake directory if it exists
      const pakeDir = path.join(config.PROJECT_ROOT, "src-tauri", ".pake");
      if (fs.existsSync(pakeDir)) {
        fs.rmSync(pakeDir, { recursive: true, force: true });
      }

      const localTestFile = path.join(config.PROJECT_ROOT, "test-local.html");
      if (fs.existsSync(localTestFile)) {
        fs.rmSync(localTestFile, { force: true });
      }
    } catch (e) {
      console.warn("   [Warn]  Cleanup warning:", e.message);
    }
  }

  displayFinalResults() {
    const passed = this.results.filter((r) => r.passed).length;
    const total = this.results.length;

    console.log("\nOverall Test Summary");
    console.log("====================");
    console.log(`Total: ${passed}/${total} tests passed`);

    if (passed === total) {
      console.log("All tests passed! CLI is ready for use.\n");
    } else {
      console.log(
        `[FAIL] ${total - passed} test(s) failed. Please check the issues above.\n`,
      );

      // Show failed tests
      const failed = this.results.filter((r) => !r.passed);
      if (failed.length > 0) {
        console.log("Failed tests:");
        failed.forEach((result) => {
          const error = result.error ? ` (${result.error})` : "";
          console.log(`  [FAIL] ${result.name}${error}`);
        });
        console.log();
      }
    }
  }
}

import ReleaseBuildTest from "./release.js";

// Command line interface
const args = process.argv.slice(2);

// Complete test suite by default - no more smart modes
const options = {
  unit: !args.includes("--no-unit"),
  integration: !args.includes("--no-integration"),
  builder: !args.includes("--no-builder"),
  pakeCliTests: args.includes("--pake-cli"),
  e2e: args.includes("--e2e"),
  realBuild: !args.includes("--no-build"), // Always include real build test
  quick: false,
};

// Help message
if (args.includes("--help") || args.includes("-h")) {
  console.log(`
[Run] Pake CLI Test Suite

Usage: npm test [-- options]

Complete Test Suite (Default):
  pnpm test                   # Run complete test suite with real build (8-12 minutes)

Test Components:
  [PASS] Unit Tests               # CLI commands, validation, response time
  [PASS] Integration Tests        # Process spawning, file permissions, dependencies
  [PASS] Builder Tests           # Platform detection, architecture, file naming
  [PASS] Real Build Test         # Complete GitHub.com app build with packaging

Optional Components:
  --e2e          Add end-to-end configuration tests
  --pake-cli     Add pake-cli GitHub Actions tests
  --release      Run release workflow tests (Twitter/WeRead) - Slow!

Skip Components (if needed):
  --no-unit      Skip unit tests
  --no-integration  Skip integration tests
  --no-builder   Skip builder tests
  --no-build     Skip real build test

Examples:
  npm test                         # Complete test suite (recommended)
  npm test -- --release           # Run everything including release workflow
  pnpm test -- --no-build         # Skip real build (faster for development)

Environment:
  CI=1              # Enable CI mode
  DEBUG=1           # Enable debug output
  PAKE_CREATE_APP=1 # Allow app creation in tests
`);
  process.exit(0);
}

// Run tests
const runner = new PakeTestRunner();
runner
  .runAll(options)
  .then(async (success) => {
    // Run release workflow tests as part of the standard suite
    // We skip this if builder tests are explicitly disabled (often used for quick checks)
    if (success && options.realBuild) {
      console.log("\n[Package] Running Release Workflow Test...");
      console.log(
        "   (This mimics the GitHub Actions release process for popular apps)",
      );

      // Pass skipCliBuild=true since "npm test" already builds the CLI
      const releaseTester = new ReleaseBuildTest();
      const releaseSuccess = await releaseTester.run({ skipCliBuild: true });

      if (!releaseSuccess) {
        console.error("\n[FAIL] Release workflow tests failed");
        process.exit(1);
      }
    }

    process.exit(success ? 0 : 1);
  })
  .catch((error) => {
    console.error("Test runner failed:", error);
    process.exit(1);
  });

export default runner;
