// buildGlobalHooks.int.test.ts
import { describe, it, expect, beforeEach, vi } from "vitest";
import * as fs from "node:fs/promises";
import * as fsSync from "node:fs";
import * as vite from "vite";
import {
  getGlobalHooksMeta,
  getComponentLibraryConfig,
  getContentHash,
} from "@embeddable.com/sdk-utils";

import buildGlobalHooks from "../src/buildGlobalHooks";
import type { ResolvedEmbeddableConfig } from "./defineConfig";
import path from "node:path";

const rootDir = path.resolve("fake", "root");
const buildDir = path.resolve("fake", "build");
const srcDir = path.resolve("fake", "src");
const lifecycleFile = path.resolve("fake", "root", "embeddable.lifecycle.ts");
const themFile = path.resolve("fake", "root", "embeddable.theme.ts");

// Potential partial mocks, or we can let some logic run real
vi.mock("node:fs/promises");
vi.mock("node:fs");
vi.mock("vite");
vi.mock("@embeddable.com/sdk-utils", async () => {
  const actual = await vi.importActual<
    typeof import("@embeddable.com/sdk-utils")
  >("@embeddable.com/sdk-utils");
  return {
    ...actual,
    getGlobalHooksMeta: vi.fn(),
    getComponentLibraryConfig: vi.fn(),
    getContentHash: vi.fn(),
  };
});

describe("buildGlobalHooks (Integration Tests)", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("builds aggregator and lifecycle for multiple libraries, then saves meta", async () => {
    // We pretend that the lifecycle file does exist
    vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => {
      // We'll say yes for lifecycle + local theme
      if (p === lifecycleFile) return true;
      if (p === themFile) return true;
      return false;
    });

    // aggregator template or any read
    (fs.readFile as any).mockResolvedValueOnce(
      "{{LIBRARY_THEME_IMPORTS}}{{LOCAL_THEME_IMPORT}}",
    );
    // Suppose subsequent reads for the entry files also return "some content"
    (fs.readFile as any).mockResolvedValue("some content");
    // We hash them all
    (getContentHash as any).mockReturnValue("123abc");
    // Suppose vite.build calls are successful
    (vite.build as any).mockResolvedValue(undefined);

    // For each library
    (getComponentLibraryConfig as any).mockImplementation((cfg: any) => ({
      libraryName: cfg.name,
    }));

    (getGlobalHooksMeta as any)
      // aggregator calls for library #1
      .mockResolvedValueOnce({
        themeProvider: "libA-theme.js",
        lifecycleHooks: ["libA-lifecycle.js"],
      })
      // aggregator calls for library #2
      .mockResolvedValueOnce({
        themeProvider: "libB-theme.js",
        lifecycleHooks: [],
      })
      // lifecycle calls for library #1
      .mockResolvedValueOnce({
        themeProvider: "libA-theme.js",
        lifecycleHooks: ["libA-lifecycle.js"],
      })
      // lifecycle calls for library #2
      .mockResolvedValueOnce({
        themeProvider: "libB-theme.js",
        lifecycleHooks: ["libB-lifecycle.js"],
      });

    const ctx: ResolvedEmbeddableConfig = {
      client: {
        srcDir,
        buildDir,
        rootDir,
        lifecycleHooksFile: lifecycleFile,
        customizationFile: themFile,
        componentLibraries: [{ name: "libA" }, { name: "libB" }],
      },
      core: {
        templatesDir: "/fake/templates",
      },
      dev: {
        watch: false, // so we do hashing, no watchers
      },
    } as any;

    await buildGlobalHooks(ctx);

    // aggregator => built with "embeddable-theme-123abc"
    expect(vite.build).toHaveBeenCalledWith(
      expect.objectContaining({
        build: expect.objectContaining({
          lib: expect.objectContaining({
            entry: expect.stringContaining("embeddableThemeHook.js"),
            fileName: "embeddable-theme-123abc",
          }),
        }),
      }),
    );

    // We also expect the lifecycle for the repo => "embeddable.lifecycle.ts"
    expect(vite.build).toHaveBeenCalledWith(
      expect.objectContaining({
        build: expect.objectContaining({
          lib: expect.objectContaining({
            entry: lifecycleFile,
            fileName: "embeddable-lifecycle", // or with hash if code does that
          }),
        }),
      }),
    );

    // library #1 => has "libA-lifecycle.js"
    // library #2 => has "libB-lifecycle.js"
    // so we expect them to build also
    expect(vite.build).toHaveBeenCalledWith(
      expect.objectContaining({
        build: expect.objectContaining({
          lib: expect.objectContaining({
            entry: path.resolve(
              rootDir,
              "node_modules",
              "libA",
              "dist",
              "libA-lifecycle.js",
            ),
          }),
        }),
      }),
    );
    expect(vite.build).toHaveBeenCalledWith(
      expect.objectContaining({
        build: expect.objectContaining({
          lib: expect.objectContaining({
            entry: path.resolve(
              rootDir,
              "node_modules",
              "libB",
              "dist",
              "libB-lifecycle.js",
            ),
          }),
        }),
      }),
    );

    // You might also check the final "saveGlobalHooksMeta" by reading the file or checking fsSync calls
    // ...
  });

  it("skips aggregator if no library has themeProvider, no local theme", async () => {
    vi.spyOn(fsSync, "existsSync").mockImplementation((p: any) => false);
    (fs.readFile as any).mockResolvedValueOnce("{{LIBRARY_THEME_IMPORTS}}");
    (getContentHash as any).mockReturnValue("someHash");
    (vite.build as any).mockResolvedValue(undefined);

    (getGlobalHooksMeta as any)
      // aggregator calls:
      .mockResolvedValueOnce({ themeProvider: null, lifecycleHooks: [] })
      // lifecycle calls:
      .mockResolvedValueOnce({ themeProvider: null, lifecycleHooks: [] });

    (getComponentLibraryConfig as any).mockImplementation((cfg: any) => ({
      libraryName: cfg.name,
    }));

    const ctx: ResolvedEmbeddableConfig = {
      client: {
        srcDir,
        buildDir,
        rootDir,
        lifecycleHooksFile: lifecycleFile,
        customizationFile: themFile,
        componentLibraries: [{ name: "libA" }],
      },
      core: {
        templatesDir: "/fake/templates",
      },
      dev: { watch: false },
    } as any;

    await buildGlobalHooks(ctx);

    // aggregator not built
    expect(vite.build).not.toHaveBeenCalledWith(
      expect.objectContaining({
        build: expect.objectContaining({
          lib: expect.objectContaining({
            entry: expect.stringContaining("embeddableThemeHook.js"),
          }),
        }),
      }),
    );
  });
});
