import { mount } from "@vue/test-utils";
import { describe, it, expect, vi } from "vitest";

import UDrawer from "../UDrawer.vue";
import UHeader from "../../ui.text-header/UHeader.vue";

import type { Props } from "../types";

describe("UDrawer", () => {
  const modelValue = true;

  // Wait for an async component to load
  function sleep(ms: number = 0) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  // Props tests
  describe("Props", () => {
    it("ModelValue – renders when true", () => {
      const component = mount(UDrawer, {
        props: {
          modelValue,
        },
      });

      expect(component.isVisible()).toBe(modelValue);
    });

    it("ModelValue – does not render when false", () => {
      const modelValue = false;

      const component = mount(UDrawer, {
        props: {
          modelValue,
        },
      });

      expect(component.find("[vl-key='overlay']").exists()).toBe(modelValue);
    });

    it("Title – renders with title prop", () => {
      const title = "Drawer Title";

      const component = mount(UDrawer, {
        props: {
          modelValue,
          title,
        },
      });

      const header = component.findComponent(UHeader);

      expect(header.exists()).toBe(true);
      expect(header.props("label")).toBe(title);
    });

    it("Description – renders with description prop", () => {
      const title = "Drawer Title";
      const description = "Drawer Description";

      const component = mount(UDrawer, {
        props: {
          modelValue,
          title,
          description,
        },
      });

      expect(component.text()).toContain(description);
    });

    it("Position – applies correct position classes", () => {
      const positions = {
        top: ["top-0", "w-full", "h-auto"],
        bottom: ["bottom-0", "w-full", "h-auto"],
        left: ["left-0", "w-max", "h-full"],
        right: ["right-0", "w-max", "h-full"],
      };

      Object.entries(positions).forEach(([position, expectedClasses]) => {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            position: position as Props["position"],
          },
        });

        const drawerClasses = component.find("[vl-key='drawerWrapper']").attributes("class");

        expectedClasses.forEach((expectedClass) => {
          expect(drawerClasses).toContain(expectedClass);
        });
      });
    });

    it("Variant – applies correct variant classes", () => {
      const variants = {
        solid: "bg-default",
        outlined: "bg-default",
        subtle: "bg-muted",
        soft: "bg-muted",
      };

      Object.entries(variants).forEach(([variant, expectedClasses]) => {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            variant: variant as Props["variant"],
          },
        });

        expect(component.find("[vl-key='drawerWrapper']").attributes("class")).toContain(
          expectedClasses,
        );
      });
    });

    it("Handle – renders handle when prop is true", () => {
      const handle = [true, false];

      handle.forEach((value) => {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            handle: value,
          },
        });

        const handleWrapper = component.find("[vl-key='handleWrapper']");
        const handleElement = component.find("[vl-key='handle']");

        expect(handleWrapper.exists()).toBe(value);
        expect(handleElement.exists()).toBe(value);
      });
    });

    it("Inset – applies inset class when prop is true", () => {
      const inset = true;
      const expectedClass = "m-4";

      const component = mount(UDrawer, {
        props: {
          modelValue,
          inset,
        },
      });

      const innerWrapper = component.find("[vl-key='innerWrapper']");

      expect(innerWrapper.attributes("class")).toContain(expectedClass);
    });

    it("CloseOnOverlay – closes drawer when overlay is clicked", () => {
      const closeOnOverlay = [true, false];

      closeOnOverlay.forEach(async (value) => {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            closeOnOverlay: value,
          },
        });

        const innerWrapper = component.find('[vl-key="innerWrapper"]');

        expect(innerWrapper.exists()).toBe(true);

        await innerWrapper.trigger("mousedown");
        await innerWrapper.trigger("click");
        await sleep(1000);

        const drawer = component.find('[vl-key="drawer"]');

        expect(drawer.exists()).toBe(!value);
      });
    });

    it("CloseOnEsc – closes drawer when escape key is pressed", () => {
      const closeOnEsc = [true, false];

      closeOnEsc.forEach(async (value) => {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            closeOnEsc: value,
          },
        });

        const wrapper = component.find("[vl-key='wrapper']");

        await wrapper.trigger("keydown", { key: "Escape" });
        await sleep(1000);

        const drawer = component.find('[vl-key="drawer"]');

        expect(drawer.exists()).toBe(!value);
      });
    });

    it("DataTest – applies the correct data-test attribute", () => {
      const dataTest = "drawer-test";

      const component = mount(UDrawer, {
        props: {
          modelValue,
          dataTest,
        },
      });

      const drawerWrapper = component.find("[vl-key='wrapper']");

      expect(drawerWrapper.attributes("data-test")).toBe(dataTest);
    });

    it("CloseOnCross – shows cross by default and closes on click when true", async () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
          closeOnCross: true,
        },
      });

      const closeButton = component.find('[vl-key="closeButton"]');

      expect(closeButton.exists()).toBe(true);

      await closeButton.trigger("click");

      expect(component.emitted("update:modelValue")).toBeTruthy();
      expect(component.emitted("update:modelValue")?.[0]).toEqual([false]);
      expect(component.emitted("close")).toBeTruthy();
    });

    it("CloseOnCross – hides cross when false", () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
          closeOnCross: false,
        },
      });

      const closeButton = component.find('[vl-key="closeButton"]');

      expect(closeButton.exists()).toBe(false);
    });
  });

  // Slots tests
  describe("Slots", () => {
    it("Default – renders content in default slot", () => {
      const slotClass = "default-content";
      const slotContent = "Default Content";

      const component = mount(UDrawer, {
        props: { modelValue: true },
        slots: {
          default: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);
    });

    it("Before Title – renders content in slot and shows header", () => {
      const slotClass = "before-title";
      const slotContent = "Before Title";

      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
        },
        slots: {
          "before-title": `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);

      // Check that header is shown when before-title slot is provided
      const header = component.find("[vl-key='header']");

      expect(header.exists()).toBe(true);
      expect(header.text()).toContain(slotContent);
    });

    it("Title – renders custom content in slot and shows header", () => {
      const slotClass = "custom-title";
      const slotContent = "Custom Title";

      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
        },
        slots: {
          title: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);
      expect(component.findComponent(UHeader).exists()).toBe(false);

      // Check that header is shown when title slot is provided
      const header = component.find("[vl-key='header']");

      expect(header.exists()).toBe(true);
      expect(header.text()).toContain(slotContent);
    });

    it("After Title – renders content in slot and shows header", () => {
      const slotClass = "after-title";
      const slotContent = "After Title";

      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
        },
        slots: {
          "after-title": `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);

      // Check that header is shown when after-title slot is provided
      const header = component.find("[vl-key='header']");

      expect(header.exists()).toBe(true);
      expect(header.text()).toContain(slotContent);
    });

    it("Actions – renders custom content in slot and shows header", () => {
      const slotClass = "actions";
      const slotContent = "Actions";

      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
        },
        slots: {
          actions: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);

      // Check that header is shown when actions slot is provided
      const header = component.find("[vl-key='header']");

      expect(header.exists()).toBe(true);
      expect(header.text()).toContain(slotContent);
    });

    it("Header – does not show when no title or slots are provided", () => {
      const component = mount(UDrawer, {
        props: { modelValue },
      });

      const header = component.find("[vl-key='header']");

      expect(header.exists()).toBe(false);
    });

    it("Actions – provides close binding to slot", () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          title: "Drawer Title",
        },
        slots: {
          actions: `
            <template #default="{ close }">
              <button class="custom-close" @click="close">Close</button>
            </template>
          `,
        },
      });

      const closeButton = component.find(".custom-close");

      expect(closeButton.exists()).toBe(true);

      // Click the close button
      closeButton.trigger("click");

      // Check if the drawer emitted the update:modelValue event with false
      expect(component.emitted("update:modelValue")).toBeTruthy();
      expect(component.emitted("update:modelValue")?.[0]).toEqual([false]);
    });

    it("Handle – renders custom content in slot", () => {
      const slotClass = "custom-handle";
      const slotContent = "Custom Handle";

      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          handle: true,
        },
        slots: {
          handle: `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);

      // Check that default handle element is not rendered when slot is used
      const defaultHandle = component.find("[vl-key='handle']");

      expect(defaultHandle.exists()).toBe(false);
    });

    it("Footer Left – renders content in slot and shows footer", () => {
      const slotClass = "footer-left";
      const slotContent = "Footer Left";

      const component = mount(UDrawer, {
        props: { modelValue: true },
        slots: {
          "footer-left": `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);

      // Check that footer is shown when footer-left slot is provided
      const footer = component.find("[vl-key='footer']");

      expect(footer.exists()).toBe(true);
      expect(footer.text()).toContain(slotContent);
    });

    it("Footer Right – renders content in slot and shows footer", () => {
      const slotClass = "footer-right";
      const slotContent = "Footer Right";

      const component = mount(UDrawer, {
        props: { modelValue: true },
        slots: {
          "footer-right": `<div class="${slotClass}">${slotContent}</div>`,
        },
      });

      expect(component.find(`.${slotClass}`).exists()).toBe(true);
      expect(component.text()).toContain(slotContent);

      // Check that footer is shown when footer-right slot is provided
      const footer = component.find("[vl-key='footer']");

      expect(footer.exists()).toBe(true);
      expect(footer.text()).toContain(slotContent);
    });

    it("Footer – does not show when no footer slots are provided", () => {
      const component = mount(UDrawer, {
        props: { modelValue },
      });

      const footer = component.find("[vl-key='footer']");

      expect(footer.exists()).toBe(false);
    });
  });

  // Events tests
  describe("Events", () => {
    it("Update:modelValue – emits event when drawer is closed", async () => {
      const title = "Drawer Title";

      const component = mount(UDrawer, {
        props: {
          modelValue,
          title,
        },
        slots: {
          actions: `
            <template #default="{ close }">
              <button class="close-btn" @click="close">Close</button>
            </template>
          `,
        },
      });

      const closeButton = component.find(".close-btn");

      await closeButton.trigger("click");

      expect(component.emitted("update:modelValue")).toBeTruthy();
      expect(component.emitted("update:modelValue")?.[0]).toEqual([false]);
    });

    it("Close – emits event when drawer is closed", async () => {
      const title = "Drawer Title";

      const component = mount(UDrawer, {
        props: {
          modelValue,
          title,
        },
        slots: {
          actions: `
            <template #default="{ close }">
              <button class="close-btn" @click="close">Close</button>
            </template>
          `,
        },
      });

      const closeButton = component.find(".close-btn");

      await closeButton.trigger("click");

      expect(component.emitted("close")).toBeTruthy();
    });

    it("CloseOnOverlay – emits events when overlay is clicked based on prop", async () => {
      const closeOnOverlay = [true, false];

      for (const value of closeOnOverlay) {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            closeOnOverlay: value,
          },
        });

        const innerWrapper = component.find("[vl-key='innerWrapper']");

        await innerWrapper.trigger("mousedown");
        await innerWrapper.trigger("click");

        expect(component.emitted("update:modelValue")).toEqual(value ? [[false]] : undefined);
        expect(Boolean(component.emitted("close")?.length)).toBe(value);
      }
    });

    it("CloseOnOverlay – does not close when mousedown on drawer and mouseup on overlay", async () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          closeOnOverlay: true,
        },
      });

      const drawer = component.find("[vl-key='drawer']");
      const innerWrapper = component.find("[vl-key='innerWrapper']");

      // Mousedown on drawer content
      await drawer.trigger("mousedown");
      // Click (mouseup) on overlay
      await innerWrapper.trigger("click");

      // Drawer should NOT close
      expect(component.emitted("update:modelValue")).toBeFalsy();
      expect(component.emitted("close")).toBeFalsy();
    });

    it("CloseOnEsc – emits events when escape key is pressed based on prop", async () => {
      const closeOnEsc = [true, false];

      for (const value of closeOnEsc) {
        const component = mount(UDrawer, {
          props: {
            modelValue,
            closeOnEsc: value,
          },
        });

        const wrapper = component.find("[vl-key='wrapper']");

        await wrapper.trigger("keydown", { key: "Escape" });

        expect(component.emitted("update:modelValue")).toEqual(value ? [[false]] : undefined);
        expect(Boolean(component.emitted("close")?.length)).toBe(value);
      }
    });
  });

  // Drag functionality tests
  describe("Drag Functionality", () => {
    it("Cursor – applies drag cursor classes when handle is enabled", () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          handle: true,
        },
      });

      const handleWrapper = component.find("[vl-key='handleWrapper']");

      expect(handleWrapper.attributes("class")).toContain("cursor-grab");
    });

    it("Mouse Drag – handles drag start from handle", async () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          handle: true,
        },
      });

      const drawer = component.find("[vl-key='drawerWrapper']");
      const handleWrapper = component.find("[vl-key='handleWrapper']");

      // Mock getBoundingClientRect
      const mockRect = {
        width: 300,
        height: 400,
        top: 0,
        left: 0,
        right: 300,
        bottom: 400,
      };

      vi.spyOn(drawer.element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);

      await handleWrapper.trigger("mousedown", {
        clientX: 100,
        clientY: 100,
      });

      // Check if handle has drag cursor class
      expect(handleWrapper.attributes("class")).toContain("cursor-grab");
    });

    it("Touch Drag – handles drag start from handle", async () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          handle: true,
        },
      });

      const drawer = component.find("[vl-key='drawerWrapper']");
      const handleWrapper = component.find("[vl-key='handleWrapper']");

      // Mock getBoundingClientRect
      const mockRect = {
        width: 300,
        height: 400,
        top: 0,
        left: 0,
        right: 300,
        bottom: 400,
      };

      vi.spyOn(drawer.element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);

      await handleWrapper.trigger("touchstart", {
        touches: [{ clientX: 100, clientY: 100 }],
      });

      // Check if handle has drag cursor class
      expect(handleWrapper.attributes("class")).toContain("cursor-grab");
    });

    it("Transform – applies drag transform styles during drag", async () => {
      const component = mount(UDrawer, {
        props: {
          modelValue: true,
          position: "left",
        },
      });

      const drawer = component.find("[vl-key='drawerWrapper']");

      // Mock getBoundingClientRect
      const mockRect = {
        width: 300,
        height: 400,
        top: 0,
        left: 0,
        right: 300,
        bottom: 400,
      };

      vi.spyOn(drawer.element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);

      // Start drag
      await drawer.trigger("mousedown", {
        clientX: 100,
        clientY: 100,
      });

      // Simulate drag movement
      const mouseMoveEvent = new MouseEvent("mousemove", {
        clientX: 150, // 50px movement
        clientY: 100,
      });

      document.dispatchEvent(mouseMoveEvent);

      // Check if transform is applied
      const drawerElement = component.find("[vl-key='drawerWrapper']");
      const style = drawerElement.attributes("style");

      expect(style ? style.includes("transform") : drawerElement.exists()).toBe(true);
    });
  });

  // Exposed refs tests
  describe("Exposed refs", () => {
    it("WrapperRef – exposes wrapper element ref", () => {
      const component = mount(UDrawer, {
        props: { modelValue },
      });

      expect(component.vm.wrapperRef).toBeDefined();
      expect(component.vm.wrapperRef instanceof HTMLDivElement).toBe(true);
    });
  });
});
