import { describe, it, beforeEach, afterEach } from "node:test";
import assert from "node:assert";
import "../test-setup.ts";
import { createRoot, type Root } from "react-dom/client";
import { act } from "@testing-library/react";
import { ModelControls } from "./ModelControls.tsx";
import { useStore } from "../store/store.ts";

describe("ModelControls", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
    useStore.setState({
      models: [],
      selectedModelId: null,
      cameraView: "perspective",
      backdropUrl: null,
      availableModels: [
        { id: "servbot", name: "Servbot", url: "/models/servbot.gltf" },
      ],
    });
  });

  afterEach(() => {
    act(() => {
      root.unmount();
    });
    container.remove();
  });

  it("should not render when no model is selected", async () => {
    await act(async () => {
      root.render(<ModelControls />);
    });

    const controls = container.querySelector(".model-controls");
    assert.ok(
      !controls,
      "Should not render controls when no model is selected",
    );
  });

  it("should render when a model is selected", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const controls = container.querySelector(".model-controls");
    assert.ok(controls, "Should render controls when model is selected");
  });

  it("should have position controls", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const positionXInput = container.querySelector('input[name="position-x"]');
    const positionYInput = container.querySelector('input[name="position-y"]');
    const positionZInput = container.querySelector('input[name="position-z"]');

    assert.ok(positionXInput, "Should have position X input");
    assert.ok(positionYInput, "Should have position Y input");
    assert.ok(positionZInput, "Should have position Z input");
  });

  it("should have rotation controls", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const rotationXInput = container.querySelector('input[name="rotation-x"]');
    const rotationYInput = container.querySelector('input[name="rotation-y"]');
    const rotationZInput = container.querySelector('input[name="rotation-z"]');

    assert.ok(rotationXInput, "Should have rotation X input");
    assert.ok(rotationYInput, "Should have rotation Y input");
    assert.ok(rotationZInput, "Should have rotation Z input");
  });

  it("should update position when input changes", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const positionXInput = container.querySelector(
      'input[name="position-x"]',
    ) as HTMLInputElement;

    await act(async () => {
      const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
        window.HTMLInputElement.prototype,
        "value",
      )!.set!;
      nativeInputValueSetter.call(positionXInput, "5");
      positionXInput.dispatchEvent(new Event("input", { bubbles: true }));
    });

    const model = useStore.getState().models[0];
    assert.strictEqual(model.position.x, 5);
  });

  it("should have hue shift controls", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const hueRedInput = container.querySelector('input[name="hue-red"]');
    const hueGreenInput = container.querySelector('input[name="hue-green"]');
    const hueBlueInput = container.querySelector('input[name="hue-blue"]');

    assert.ok(hueRedInput, "Should have red hue shift input");
    assert.ok(hueGreenInput, "Should have green hue shift input");
    assert.ok(hueBlueInput, "Should have blue hue shift input");
  });

  it("should update hue shift when slider changes", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const hueRedInput = container.querySelector(
      'input[name="hue-red"]',
    ) as HTMLInputElement;

    await act(async () => {
      const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
        window.HTMLInputElement.prototype,
        "value",
      )!.set!;
      nativeInputValueSetter.call(hueRedInput, "0.5");
      hueRedInput.dispatchEvent(new Event("input", { bubbles: true }));
    });

    const model = useStore.getState().models[0];
    assert.strictEqual(model.hueShift.red, 0.5);
  });

  it("should have a delete button", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const deleteButton = container.querySelector("button.delete-model");
    assert.ok(deleteButton, "Should have delete button");
  });

  it("should remove model when delete button is clicked", async () => {
    useStore.getState().addModel("servbot", { x: 0, y: 0, z: 0 });
    const modelId = useStore.getState().models[0].id;
    useStore.getState().selectModel(modelId);

    await act(async () => {
      root.render(<ModelControls />);
    });

    const deleteButton = container.querySelector(
      "button.delete-model",
    ) as HTMLButtonElement;

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

    const models = useStore.getState().models;
    assert.strictEqual(models.length, 0, "Model should be removed");
    assert.strictEqual(
      useStore.getState().selectedModelId,
      null,
      "Selection should be cleared",
    );
  });
});
