// SaveLoadPanel.all.test.tsx
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import useFlowStore from '../../../../../src/pages/VisProgPage/visualProgrammingUI/VisProgStores.tsx';
import SaveLoadPanel from '../../../../../src/pages/VisProgPage/visualProgrammingUI/components/SaveLoadPanel.tsx';
import { makeProjectBlob } from '../../../../../src/utils/SaveLoad.ts';
import { mockReactFlow } from "../../../../setupFlowTests.ts"; // optional helper if present

// helper to read Blob contents in tests (works in Node/Jest env)
async function blobToText(blob: Blob): Promise<string> {
  if (typeof (blob as any).text === "function") return await (blob as any).text();
  if (typeof (blob as any).arrayBuffer === "function") {
    const buf = await (blob as any).arrayBuffer();
    return new TextDecoder().decode(buf);
  }
  return await new Promise<string>((resolve, reject) => {
    const fr = new FileReader();
    fr.onload = () => resolve(String(fr.result));
    fr.onerror = () => reject(fr.error);
    fr.readAsText(blob);
  });
}

beforeAll(() => {
  // if you have a mockReactFlow helper used in other tests, call it
  if (typeof mockReactFlow === "function") mockReactFlow();
});

beforeEach(() => {
  // clear and seed the zustand store to a known empty state
  act(() => {
    const { setNodes, setEdges } = useFlowStore.getState();
    setNodes([]);
    setEdges([]);
  });

  // Ensure URL.createObjectURL exists so jest.spyOn works
  if (!URL.createObjectURL) URL.createObjectURL = jest.fn();
});

afterEach(() => {
  jest.restoreAllMocks();
});

describe("SaveLoadPanel - combined tests", () => {
  test("makeProjectBlob creates a valid JSON blob", async () => {
    const nodes = [
      {
        id: "n1",
        type: "start",
        position: { x: 0, y: 0 },
        data: { label: "Start" },
      } as any,
    ];
    const edges: any[] = [];

    const blob = makeProjectBlob("my-project", nodes, edges);
    expect(blob).toBeInstanceOf(Blob);

    const text = await blobToText(blob);
    const parsed = JSON.parse(text);

    expect(parsed.name).toBe("my-project");
    expect(typeof parsed.savedAt).toBe("string");
    expect(Array.isArray(parsed.nodes)).toBe(true);
    expect(Array.isArray(parsed.edges)).toBe(true);
    expect(parsed.nodes).toEqual(nodes);
    expect(parsed.edges).toEqual(edges);
  });

  test("onSave creates a blob URL and sets anchor href", async () => {
    // Seed the store so onSave has nodes to save
    act(() => {
      useFlowStore.getState().setNodes([
        { id: "start", type: "start", position: { x: 0, y: 0 }, data: { label: "start" } } as any,
      ]);
      useFlowStore.getState().setEdges([]);
    });

    // Ensure createObjectURL exists and spy it
    if (!URL.createObjectURL) URL.createObjectURL = jest.fn();
    const createObjectURLSpy = jest.spyOn(URL, "createObjectURL").mockReturnValue("blob:fake-url");

    render(<SaveLoadPanel />);

    const saveAnchor = screen.getByText(/Save Graph/i) as HTMLAnchorElement;

    await act(async () => {
      fireEvent.click(saveAnchor);
    });

    expect(createObjectURLSpy).toHaveBeenCalledTimes(1);
    const blobArg = createObjectURLSpy.mock.calls[0][0];
    expect(blobArg).toBeInstanceOf(Blob);

    expect(saveAnchor.getAttribute("href")).toBe("blob:fake-url");

    const text = await blobToText(blobArg as Blob);
    const parsed = JSON.parse(text);

    expect(parsed.name).toBeDefined();
    expect(parsed.nodes).toBeDefined();
    expect(parsed.edges).toBeDefined();

    createObjectURLSpy.mockRestore();
  });

  test("onLoad with invalid JSON does not update store", async () => {
    const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
  
    const file = new File(["not json"], "bad.json", { type: "application/json" });
    file.text = jest.fn(() => Promise.resolve(`{"bad json`));

    window.alert = jest.fn();

    render(<SaveLoadPanel />);
    const input = document.querySelector('input[type="file"]') as HTMLInputElement;
    
    act(() => {
      fireEvent.change(input, { target: { files: [file] } });
    });

    await waitFor(() => {
      expect(window.alert).toHaveBeenCalledTimes(1);
      const nodesAfter = useFlowStore.getState().nodes;
      expect(nodesAfter).toHaveLength(0);
    });

    // Clean up the spy
    consoleSpy.mockRestore();
  });

  test("onLoad resolves to null when no file is chosen (user cancels) and does not update store", async () => {
    render(<SaveLoadPanel />);
    const input = document.querySelector('input[type="file"]') as HTMLInputElement;
    expect(input).toBeTruthy();

    // Click Load to set resolver
    const loadButton = screen.getByLabelText(/load graph/i);

    await act(async () => {
      fireEvent.click(loadButton);
      // simulate user cancelling: change with empty files
      fireEvent.change(input, { target: { files: [] } });
      await Promise.resolve();
    });

    await waitFor(() => {
      const nodesAfter = useFlowStore.getState().nodes;
      const edgesAfter = useFlowStore.getState().edges;
      expect(nodesAfter).toHaveLength(0);
      expect(edgesAfter).toHaveLength(0);
      expect(input.value).toBe("");
    });
  });
});
