import { execSync } from "child_process";
import * as path from "path";
import * as fs from "fs";

jest.setTimeout(60000);

// Normalize SVG output by replacing environment-specific values
function normalizeSvg(svg: string): string {
  let normalized = svg;

  // Remove duplicate error SVG if present (PlantUML generates error SVG for empty diagrams)
  const closingSvgTag = "</svg>";
  const firstSvgEnd = normalized.indexOf(closingSvgTag);
  if (firstSvgEnd !== -1) {
    const afterFirstSvg = normalized.substring(
      firstSvgEnd + closingSvgTag.length,
    );
    if (afterFirstSvg.includes('<?xml version="1.0"')) {
      // Multiple SVG documents found, keep only the first one
      normalized = normalized.substring(0, firstSvgEnd + closingSvgTag.length);
    }
  }

  return (
    normalized
      // Normalize PlantUML filter IDs (random per run) - must be before number normalization
      .replace(/id="f[a-z0-9]+"/g, 'id="FILTER"')
      .replace(/url\(#f[a-z0-9]+\)/g, "url(#FILTER)")
      // Strip path/polygon elements entirely (different GraphViz versions generate different geometries)
      .replace(/<path[^/]*\/>/g, "")
      .replace(/<polygon[^/]*\/>/g, "")
      .replace(/<line[^/]*\/>/g, "")
      // Normalize all numeric values (coordinates, dimensions differ between GraphViz versions)
      .replace(/\d+(\.\d+)?/g, "0")
      // Normalize PlantUML version timestamp (timezone variations)
      .replace(/PlantUML version [^\n]+/g, "PlantUML version NORMALIZED")
      // Normalize operating system name (macOS vs Linux in CI)
      .replace(/Operating System: [^\n<]+/g, "Operating System: NORMALIZED")
      // Normalize Java version
      .replace(/Java Version: [^\n<]+/g, "Java Version: NORMALIZED")
      // Normalize OS version
      .replace(/OS Version: [^\n<]+/g, "OS Version: NORMALIZED")
      // Normalize country (can be null or US)
      .replace(/Country: [^\n<]+/g, "Country: NORMALIZED")
      // Normalize machine name
      .replace(/Machine: [^\n<]+/g, "Machine: NORMALIZED")
      // Normalize memory values (can vary)
      .replace(/Max Memory: [\d,]+/g, "Max Memory: NORMALIZED")
      .replace(/Total Memory: [\d,]+/g, "Total Memory: NORMALIZED")
      .replace(/Free Memory: [\d,]+/g, "Free Memory: NORMALIZED")
      .replace(/Used Memory: [\d,]+/g, "Used Memory: NORMALIZED")
  );
}

describe("CLI", () => {
  const arkit = path.resolve(__dirname, "../index.js");
  const exec = (command: string): string => {
    return execSync(command).toString().split(__dirname).join("__dirname");
  };

  describe("Options", () => {
    test("should output help", () => {
      expect(exec(`${arkit} -h`)).toMatchSnapshot();
    });
  });

  describe("Arkit", () => {
    describe("package.json", () => {
      const svgPath = path.resolve(__dirname, "../dist/arkit.svg");
      const pngPath = path.resolve(__dirname, "../dist/arkit.png");

      beforeAll(() => {
        if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath);
        if (fs.existsSync(pngPath)) fs.unlinkSync(pngPath);

        exec(`npm run architecture`);
      });

      test("should generate correct png", () => {
        const stat = fs.statSync(pngPath);

        // Verify file exists and has reasonable size (not empty, not too large)
        expect(stat.size).toBeGreaterThan(1000); // At least 1KB
        expect(stat.size).toBeLessThan(200000); // Less than 200KB

        // Verify it's a valid PNG by checking the header
        const buffer = fs.readFileSync(pngPath);
        const pngHeader = Buffer.from([
          0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
        ]);
        expect(buffer.subarray(0, 8).equals(pngHeader)).toBe(true);
      });

      test("should generate correct svg", () => {
        const svg = fs.readFileSync(svgPath).toString();
        expect(normalizeSvg(svg)).toMatchSnapshot();
      });
    });
  });

  describe("Sample", () => {
    describe("no args", () => {
      const dir = path.resolve(__dirname, "./sample");
      const pumlPath = path.resolve(dir, "./docs/architecture.puml");
      const svgPath = path.resolve(dir, "./docs/architecture.svg");

      beforeAll(() => {
        if (fs.existsSync(pumlPath)) fs.unlinkSync(pumlPath);
        if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath);

        process.chdir(dir);
        exec(arkit);
      });

      test("should generate correct puml", () => {
        expect(fs.readFileSync(pumlPath).toString()).toMatchSnapshot();
      });

      test("should generate correct svg", () => {
        const svg = fs.readFileSync(svgPath).toString();
        expect(normalizeSvg(svg)).toMatchSnapshot();
      });
    });
  });

  describe("Angular2 Todo", () => {
    describe("no args", () => {
      test("should output correct svg", () => {
        const dir = path.resolve(__dirname, "./angular2_es2015");

        process.chdir(dir);
        expect(exec(arkit)).toMatchSnapshot();
      });
    });

    describe("exclude and puml", () => {
      test("should output correct puml", () => {
        const dir = path.resolve(__dirname, "./angular2_es2015");

        process.chdir(dir);
        expect(
          exec(`${arkit} -o puml -e "app/components/**"`),
        ).toMatchSnapshot();
      });
    });
  });

  describe("Express", () => {
    describe("no args", () => {
      test("should output correct svg", () => {
        const dir = path.resolve(__dirname, "./express");
        const svgPath = path.resolve(dir, "./express.svg");

        if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath);

        process.chdir(dir);
        exec(arkit);

        expect(
          normalizeSvg(fs.readFileSync(svgPath).toString()),
        ).toMatchSnapshot();
      });
    });
  });

  describe("ReactDOM", () => {
    describe("config path", () => {
      test("should output correct svg", () => {
        const dir = path.resolve(__dirname, "./react-dom");
        const svgPath = path.resolve(dir, "./arkit.svg");

        if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath);

        process.chdir(dir);
        exec(`${arkit} -c react-arkit.json`);

        expect(
          normalizeSvg(fs.readFileSync(svgPath).toString()),
        ).toMatchSnapshot();
      });
    });
  });
});
