import { describe, it, expect } from "vitest";
import { render, screen, act } from "@testing-library/react";
import React from "react";
import { EditorProvider, useEditor } from "../EditorContext";

const mockGameConfig = {
  gameId: "test-game",
  gameName: "Test Game",
  fases: [{ id: 1 }, { id: 2 }],
};

function ConsumerComponent() {
  const { gameNameKey, gameConfig, currentPhase, editorData, updateEditorData } =
    useEditor();
  return (
    <div>
      <span data-testid="gameNameKey">{gameNameKey}</span>
      <span data-testid="gameId">{gameConfig.gameId}</span>
      <span data-testid="currentPhase">{currentPhase}</span>
      <span data-testid="toolboxJson">{editorData.toolboxJson ?? "none"}</span>
      <button
        onClick={() => updateEditorData({ toolboxJson: "updated-toolbox" })}
      >
        Update
      </button>
    </div>
  );
}

function renderWithProvider(phase = 1) {
  return render(
    <EditorProvider gameConfig={mockGameConfig} currentPhase={phase}>
      <ConsumerComponent />
    </EditorProvider>
  );
}

describe("EditorProvider", () => {
  it("provides gameNameKey derived from gameId and currentPhase", () => {
    renderWithProvider(1);
    expect(screen.getByTestId("gameNameKey").textContent).toBe(
      "test-game-fase-1"
    );
  });

  it("updates gameNameKey when phase changes", () => {
    renderWithProvider(3);
    expect(screen.getByTestId("gameNameKey").textContent).toBe(
      "test-game-fase-3"
    );
  });

  it("provides gameConfig to consumers", () => {
    renderWithProvider();
    expect(screen.getByTestId("gameId").textContent).toBe("test-game");
  });

  it("provides currentPhase to consumers", () => {
    renderWithProvider(2);
    expect(screen.getByTestId("currentPhase").textContent).toBe("2");
  });

  it("initialises editorData as empty object", () => {
    renderWithProvider();
    expect(screen.getByTestId("toolboxJson").textContent).toBe("none");
  });

  it("updateEditorData merges new data into editorData", async () => {
    renderWithProvider();
    await act(async () => {
      screen.getByRole("button").click();
    });
    expect(screen.getByTestId("toolboxJson").textContent).toBe(
      "updated-toolbox"
    );
  });

  it("updateEditorData preserves existing keys when adding new ones", async () => {
    function MultiUpdateConsumer() {
      const { editorData, updateEditorData } = useEditor();
      return (
        <div>
          <span data-testid="key1">{editorData.key1 ?? "none"}</span>
          <span data-testid="key2">{editorData.key2 ?? "none"}</span>
          <button
            data-testid="add-key1"
            onClick={() => updateEditorData({ key1: "value1" })}
          />
          <button
            data-testid="add-key2"
            onClick={() => updateEditorData({ key2: "value2" })}
          />
        </div>
      );
    }
    render(
      <EditorProvider gameConfig={mockGameConfig} currentPhase={1}>
        <MultiUpdateConsumer />
      </EditorProvider>
    );
    await act(async () => {
      screen.getByTestId("add-key1").click();
    });
    await act(async () => {
      screen.getByTestId("add-key2").click();
    });
    expect(screen.getByTestId("key1").textContent).toBe("value1");
    expect(screen.getByTestId("key2").textContent).toBe("value2");
  });

  it("does not expose phase or setEditorData in the context value", () => {
    let contextValue;
    function Inspector() {
      contextValue = useEditor();
      return null;
    }
    render(
      <EditorProvider gameConfig={mockGameConfig} currentPhase={1}>
        <Inspector />
      </EditorProvider>
    );
    expect(contextValue).not.toHaveProperty("phase");
    expect(contextValue).not.toHaveProperty("setEditorData");
  });

  it("exposes exactly the expected keys", () => {
    let contextValue;
    function Inspector() {
      contextValue = useEditor();
      return null;
    }
    render(
      <EditorProvider gameConfig={mockGameConfig} currentPhase={1}>
        <Inspector />
      </EditorProvider>
    );
    expect(Object.keys(contextValue).sort()).toEqual(
      ["currentPhase", "editorData", "gameConfig", "gameNameKey", "updateEditorData"].sort()
    );
  });
});

describe("useEditor", () => {
  it("throws when used outside EditorProvider", () => {
    const spy = vi.spyOn(console, "error").mockImplementation(() => {});
    expect(() => render(<ConsumerComponent />)).toThrow(
      "useEditor deve ser usado dentro de EditorProvider"
    );
    spy.mockRestore();
  });
});
