import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, test } from "vitest";

const root = existsSync(join(process.cwd(), "turbo.json"))
  ? process.cwd()
  : join(process.cwd(), "..", "..");

describe("workspace boundaries", () => {
  test("declares the root as a turbo-managed bun workspace", () => {
    const packageJson = JSON.parse(
      readFileSync(join(root, "package.json"), "utf8"),
    ) as {
      private?: boolean;
      scripts?: Record<string, string>;
      workspaces?: string[];
    };

    expect(packageJson.private).toBe(true);
    expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
    expect(packageJson.scripts?.build).toBe("turbo run build");
    expect(existsSync(join(root, "turbo.json"))).toBe(true);
  });

  test("keeps release orchestration in the root turbo task graph", () => {
    const rootPackage = JSON.parse(
      readFileSync(join(root, "package.json"), "utf8"),
    ) as {
      scripts?: Record<string, string>;
    };
    const webPackage = JSON.parse(
      readFileSync(join(root, "apps/web/package.json"), "utf8"),
    ) as { scripts?: Record<string, string> };
    const turboConfig = JSON.parse(
      readFileSync(join(root, "turbo.json"), "utf8"),
    ) as {
      globalDependencies?: string[];
      tasks?: Record<string, { env?: string[] }>;
    };

    expect(rootPackage.scripts?.["test:qa"]).toBe(
      "turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
    );
    expect(webPackage.scripts).not.toHaveProperty("test:qa");
    expect(turboConfig.tasks).not.toHaveProperty("test:qa");
    expect(turboConfig.globalDependencies).toEqual(
      expect.arrayContaining(["bun.lock", "tsconfig.base.json"]),
    );
    expect(turboConfig.tasks?.["test:e2e"]?.env).toEqual(
      expect.arrayContaining([
        "CI",
        "PLAYWRIGHT_DATABASE_URL",
        "PLAYWRIGHT_PORT",
        "PLAYWRIGHT_STORYBOOK_PORT",
      ]),
    );
  });

  test("lets turbo build app and Storybook artifacts before e2e serves them", () => {
    const playwrightConfig = readFileSync(
      join(root, "apps/web/playwright.config.ts"),
      "utf8",
    );
    const turboConfig = JSON.parse(
      readFileSync(join(root, "turbo.json"), "utf8"),
    ) as {
      tasks?: Record<string, { dependsOn?: string[] }>;
    };

    expect(playwrightConfig).not.toContain("bun run build &&");
    expect(playwrightConfig).not.toContain("bun run build-storybook");
    expect(turboConfig.tasks?.["test:e2e"]?.dependsOn).toEqual(
      expect.arrayContaining(["@dimensionlab/ui#build-storybook"]),
    );
  });

  test("keeps the website app and reusable UI library as separate packages", () => {
    const webPackage = JSON.parse(
      readFileSync(join(root, "apps/web/package.json"), "utf8"),
    ) as { dependencies?: Record<string, string>; name?: string };
    const uiPackage = JSON.parse(
      readFileSync(join(root, "packages/ui/package.json"), "utf8"),
    ) as { exports?: Record<string, unknown>; name?: string };

    expect(webPackage.name).toBe("@dimensionlab/web");
    expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*");
    expect(uiPackage.name).toBe("@dimensionlab/ui");
    expect(uiPackage.exports).toHaveProperty(".");
    expect(uiPackage.exports).toHaveProperty("./styles.css");
  });

  test("keeps the dashboard model in a reusable internal package", () => {
    const webPackage = JSON.parse(
      readFileSync(join(root, "apps/web/package.json"), "utf8"),
    ) as { dependencies?: Record<string, string> };
    const modelPackage = JSON.parse(
      readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
    ) as { exports?: Record<string, unknown>; name?: string };
    const tsconfig = JSON.parse(
      readFileSync(join(root, "tsconfig.json"), "utf8"),
    ) as { references?: Array<{ path: string }> };

    expect(webPackage.dependencies?.["@dimensionlab/dashboard-model"]).toBe(
      "workspace:*",
    );
    expect(modelPackage.name).toBe("@dimensionlab/dashboard-model");
    expect(modelPackage.exports).toHaveProperty(".");
    expect(modelPackage.exports).toHaveProperty("./fixtures");
    expect(tsconfig.references).toEqual(
      expect.arrayContaining([{ path: "./packages/dashboard-model" }]),
    );
    expect(existsSync(join(root, "apps/web/src/lib/model/index.ts"))).toBe(
      false,
    );
  });

  test("consumes workspace packages through package exports instead of source aliases", () => {
    const webTsconfig = JSON.parse(
      readFileSync(join(root, "apps/web/tsconfig.json"), "utf8"),
    ) as { compilerOptions?: { paths?: Record<string, string[]> } };
    const modelPackage = JSON.parse(
      readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
    ) as { exports?: Record<string, WorkspacePackageExport> };
    const uiPackage = JSON.parse(
      readFileSync(join(root, "packages/ui/package.json"), "utf8"),
    ) as { exports?: Record<string, WorkspacePackageExport> };
    const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8");
    const turboConfig = JSON.parse(
      readFileSync(join(root, "turbo.json"), "utf8"),
    ) as { tasks?: Record<string, { dependsOn?: string[] }> };

    expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
      "@dimensionlab/dashboard-model",
    );
    expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
      "@dimensionlab/dashboard-model/fixtures",
    );
    expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty("@dimensionlab/ui");
    expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
      "@dimensionlab/ui/styles.css",
    );
    expect(webTsconfig.compilerOptions?.paths).toEqual({
      "$lib/*": ["src/lib/*"],
    });
    expect(viteConfig).not.toContain("../../packages/dashboard-model/src");
    expect(viteConfig).not.toContain("../../packages/ui/src");
    expect(turboConfig.tasks?.dev?.dependsOn).toEqual(["^build"]);
    expectPackageExport(modelPackage.exports?.["."], {
      types: "./dist/index.d.ts",
      development: "./src/index.ts",
      default: "./dist/index.js",
    });
    expectPackageExport(modelPackage.exports?.["./fixtures"], {
      types: "./dist/fixtures/index.d.ts",
      development: "./src/fixtures/index.ts",
      default: "./dist/fixtures/index.js",
    });
    expectPackageExport(uiPackage.exports?.["."], {
      types: "./dist/index.d.ts",
      development: "./src/index.ts",
      default: "./dist/index.js",
    });
    expectPackageExport(uiPackage.exports?.["./styles.css"], {
      development: "./src/styles.css",
      default: "./dist/styles.css",
    });
    expectPackageExport(uiPackage.exports?.["./tokens.css"], {
      development: "./src/tokens.css",
      default: "./dist/tokens.css",
    });
  });

  test("builds the internal container from a turbo-pruned web workspace", () => {
    const containerfile = readFileSync(
      join(root, "apps/web/Containerfile"),
      "utf8",
    );
    const pruneCommand = "turbo prune @dimensionlab/web --docker";
    const jsonCopy = "COPY --from=pruner /repo/out/json/ ./";
    const sourceCopy = "COPY --from=pruner /repo/out/full/ ./";
    const tsconfigCopy =
      "COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./";
    const installCommand = "RUN bun install --frozen-lockfile";

    expect(containerfile).toContain("AS pruner");
    expect(containerfile).toContain(pruneCommand);
    expect(containerfile).toContain(jsonCopy);
    expect(containerfile).toContain(sourceCopy);
    expect(containerfile).toContain(tsconfigCopy);
    expect(containerfile).not.toContain(
      "COPY packages/dashboard-model/package.json",
    );
    expect(containerfile).not.toContain("COPY packages/ui/package.json");

    const jsonCopyIndex = containerfile.indexOf(jsonCopy);
    const installIndex = containerfile.indexOf(installCommand);
    const sourceCopyIndex = containerfile.indexOf(sourceCopy);
    const tsconfigCopyIndex = containerfile.indexOf(tsconfigCopy);

    expect(jsonCopyIndex).toBeGreaterThan(-1);
    expect(installIndex).toBeGreaterThan(jsonCopyIndex);
    expect(sourceCopyIndex).toBeGreaterThan(installIndex);
    expect(tsconfigCopyIndex).toBeGreaterThan(sourceCopyIndex);
    expect(containerfile.indexOf("RUN bun run build")).toBeGreaterThan(
      tsconfigCopyIndex,
    );
  });

  test("keeps local turbo prune output out of git and container contexts", () => {
    const webPackage = JSON.parse(
      readFileSync(join(root, "apps/web/package.json"), "utf8"),
    ) as { scripts?: Record<string, string> };
    const gitignore = readFileSync(join(root, ".gitignore"), "utf8");
    const containerignore = readFileSync(join(root, ".containerignore"), "utf8");
    const containerIgnoreRules = new Set(
      containerignore
        .split(/\r?\n/)
        .map((line) => line.trim())
        .filter(Boolean),
    );

    expect(gitignore).toContain("out/");
    expect(containerignore).toContain("out");
    expect([...containerIgnoreRules]).toEqual(
      expect.arrayContaining([
        "apps/*/.turbo",
        "apps/*/build",
        "apps/*/data",
        "apps/*/dist",
        "apps/*/playwright-report",
        "apps/*/test-results",
        "packages/*/.turbo",
        "packages/*/dist",
        "packages/*/storybook-static",
      ]),
    );
    expect(webPackage.scripts?.build).toBe(
      "rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build",
    );
  });
});

type WorkspacePackageExport =
  | string
  | {
      types?: string;
      development?: string;
      default?: string;
    };

function expectPackageExport(
  actual: WorkspacePackageExport | undefined,
  expected: Exclude<WorkspacePackageExport, string>,
): void {
  expect(actual).toMatchObject(expected);
}
