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

import type { ComponentInternalInstance } from "vue";

// TODO: Autogenerated, need to be reviewed

// Mock the ui utils
vi.mock("../../utils/ui.ts", () => ({
  cx: vi.fn((classes) => (Array.isArray(classes) ? classes.filter(Boolean).join(" ") : classes)),
  cva: vi.fn((config) => {
    return vi.fn((props) => {
      if (!config.variants) return config.base || "";

      let classes = config.base || "";

      // Apply variants
      Object.entries(config.variants).forEach(([key, variants]) => {
        const value = props[key];
        const variantsRecord = variants as UnknownObject;

        if (variantsRecord?.[value]) {
          classes += ` ${variantsRecord[value]}`;
        }
      });

      return classes.trim();
    });
  }),
  setColor: vi.fn((classes, color) => classes?.replace(/{color}/g, color)),
  vuelessConfig: { components: {}, unstyled: false },
  getMergedConfig: vi.fn((args) => {
    // Create a spy function that returns the expected merged config
    const { defaultConfig, globalConfig, propsConfig } = args;

    return {
      ...defaultConfig,
      ...globalConfig,
      ...propsConfig,
    };
  }),
}));

// Mock Vue functions
vi.mock("vue", async () => {
  const actual = await vi.importActual("vue");

  return {
    ...actual,
    getCurrentInstance: vi.fn(),
    useAttrs: vi.fn(),
  };
});

import { useUI } from "../useUI";
import * as uiUtils from "../../utils/ui";
import { getCurrentInstance, useAttrs } from "vue";
import type { UnknownObject } from "../../types";

// Test component for integration testing
const TestComponent = {
  template: '<div :data-test="getDataTest()" v-bind="bodyAttrs">Test</div>',
  props: {
    config: {
      type: Object,
      default: () => ({}),
    },
    dataTest: {
      type: String,
      default: null,
    },
    color: {
      type: String,
      default: "primary",
    },
  },
  setup() {
    const defaultConfig = {
      body: {
        base: "base-class",
        variants: {
          variant: {
            primary: "primary-class",
            secondary: "secondary-class",
          },
          size: {
            sm: "small-class",
            md: "medium-class",
          },
        },
      },
      defaults: {
        variant: "primary",
        size: "md",
      },
    };

    return useUI(defaultConfig);
  },
};

describe("useUI", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Reset vuelessConfig
    uiUtils.vuelessConfig.components = {};
    uiUtils.vuelessConfig.unstyled = false;

    // Setup default mocks
    vi.mocked(getCurrentInstance).mockReturnValue({
      type: { __name: "TestComponent" },
      props: { dataTest: "test", color: "primary", config: {} },
      parent: null,
    } as unknown as ComponentInternalInstance);

    vi.mocked(useAttrs).mockReturnValue({
      class: "",
      style: "",
    });
  });

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

  describe("Basic Functionality", () => {
    it("should return config, getDataTest, and attribute objects", () => {
      const defaultConfig = {
        body: { base: "test-class" },
        defaults: { variant: "primary" },
      };

      const result = useUI(defaultConfig);

      expect(result).toHaveProperty("config");
      expect(result).toHaveProperty("getDataTest");
      expect(result).toHaveProperty("bodyAttrs");
      expect(typeof result.getDataTest).toBe("function");
    });

    it("should merge default config with global and props config", async () => {
      const defaultConfig = {
        body: { base: "default-class" },
        defaults: { variant: "primary" },
      };

      const globalConfig = {
        body: { base: "global-class" },
      };

      const propsConfig = {
        body: { base: "props-class" },
      };

      // Set global config
      uiUtils.vuelessConfig.components = {
        TestComponent: globalConfig,
      };

      // Mock getCurrentInstance to return props config
      vi.mocked(getCurrentInstance).mockReturnValue({
        type: { __name: "TestComponent" },
        props: { config: propsConfig, dataTest: "test" },
        parent: null,
      } as unknown as ComponentInternalInstance);

      // Call useUI to trigger the config merging
      useUI(defaultConfig);

      expect(uiUtils.getMergedConfig).toHaveBeenCalledWith({
        defaultConfig,
        globalConfig,
        propsConfig,
        unstyled: false,
      });
    });
  });

  describe("CVA Integration", () => {
    it("should generate classes using CVA when config has variants", () => {
      const defaultConfig = {
        body: {
          base: "base-class",
          variants: {
            variant: {
              primary: "primary-class",
              secondary: "secondary-class",
            },
          },
        },
      };

      // Call useUI to trigger CVA usage
      useUI(defaultConfig);

      expect(uiUtils.cva).toHaveBeenCalledWith(defaultConfig.body);
    });

    it("should handle string-based config values", () => {
      const defaultConfig = {
        body: "simple-string-class",
      };

      const result = useUI(defaultConfig);
      const bodyAttrs = result.bodyAttrs;

      expect(bodyAttrs.value.class).toContain("simple-string-class");
    });
  });

  describe("Color Handling", () => {
    it("should replace {color} placeholders in classes", () => {
      // Mock getCurrentInstance to return color prop
      vi.mocked(getCurrentInstance).mockReturnValue({
        type: { __name: "TestComponent" },
        props: { color: "blue", dataTest: "test" },
        parent: null,
      } as unknown as ComponentInternalInstance);

      // Test the setColor function directly
      expect(uiUtils.setColor).toBeDefined();

      // The setColor function should replace {color} placeholders
      const testClasses = "text-{color} bg-{color}";
      const coloredClasses = uiUtils.setColor(testClasses, "blue");

      expect(coloredClasses).toBe("text-blue bg-blue");

      // Test that useUI can be called with color-containing config
      const defaultConfig = {
        wrapper: {
          base: "text-{color} bg-{color}",
        },
      };

      const result = useUI(defaultConfig);

      expect(result).toHaveProperty("config");
      expect(result).toHaveProperty("getDataTest");
    });
  });

  describe("getDataTest Function", () => {
    it("should return data-test value when dataTest prop is provided", () => {
      const result = useUI({});
      const dataTest = result.getDataTest();

      expect(dataTest).toBe("test");
    });

    it("should return data-test with suffix when suffix is provided", () => {
      const result = useUI({});
      const dataTest = result.getDataTest("button");

      expect(dataTest).toBe("test-button");
    });

    it("should return null when dataTest prop is not provided", () => {
      // Mock getCurrentInstance to return no dataTest
      vi.mocked(getCurrentInstance).mockReturnValue({
        type: { __name: "TestComponent" },
        props: {},
        parent: null,
      } as unknown as ComponentInternalInstance);

      const result = useUI({});
      const dataTest = result.getDataTest();

      expect(dataTest).toBeNull();
    });
  });

  describe("Nested Component Handling", () => {
    it("should handle nested component references like {UIcon}", () => {
      const defaultConfig = {
        icon: "{UIcon}",
        button: "btn {UIcon} end",
      };

      const result = useUI(defaultConfig);

      // The nested component pattern should be processed
      expect(result).toHaveProperty("iconAttrs");
      expect(result).toHaveProperty("buttonAttrs");
    });
  });

  describe("Reactivity", () => {
    it("should update config when props.config changes", async () => {
      const component = mount(TestComponent);

      // Initial state
      expect(component.vm.config).toBeDefined();

      // Change props
      await component.setProps({
        config: {
          body: { base: "new-class" },
        },
      });

      await nextTick();

      // Config should be updated
      expect(uiUtils.getMergedConfig).toHaveBeenCalled();
    });
  });

  describe("Extends Pattern Handling", () => {
    it("should handle extends pattern {>key} syntax", () => {
      const defaultConfig = {
        button: "base-class {>icon}",
        icon: "icon-class",
      };

      const result = useUI(defaultConfig);

      expect(result).toHaveProperty("buttonAttrs");
      expect(result).toHaveProperty("iconAttrs");
    });
  });

  describe("Mutated Props", () => {
    it("should use mutated props for class generation", () => {
      const defaultConfig = {
        body: {
          base: "base-class",
          variants: {
            hasIcon: {
              true: "with-icon",
              false: "without-icon",
            },
          },
        },
      };

      const mutatedProps = computed(() => ({
        hasIcon: true,
      }));

      const result = useUI(defaultConfig, mutatedProps);

      expect(result).toHaveProperty("bodyAttrs");
    });
  });

  describe("Component Name Detection", () => {
    it("should detect component name from type.__name", () => {
      vi.mocked(getCurrentInstance).mockReturnValue({
        type: { __name: "UButton" },
        props: { dataTest: "test" },
        parent: null,
      } as unknown as ComponentInternalInstance);

      const result = useUI({});

      expect(result).toBeDefined();
    });

    it("should detect component name from parent when internal component", () => {
      vi.mocked(getCurrentInstance).mockReturnValue({
        type: { internal: true },
        props: { dataTest: "test" },
        parent: {
          type: { __name: "UButton" },
        } as UnknownObject,
      } as unknown as ComponentInternalInstance);

      const result = useUI({});

      expect(result).toBeDefined();
    });
  });

  describe("Attribute Generation", () => {
    it("should generate proper attributes for each config key", () => {
      const defaultConfig = {
        wrapper: { base: "wrapper-class" },
        content: { base: "content-class" },
        footer: "footer-class",
      };

      const result = useUI(defaultConfig);

      expect(result).toHaveProperty("wrapperAttrs");
      expect(result).toHaveProperty("contentAttrs");
      expect(result).toHaveProperty("footerAttrs");
    });

    it("should include config in attributes for nested components", () => {
      const defaultConfig = {
        icon: {
          base: "{UIcon}",
          defaults: {
            size: "sm",
          },
        },
      };

      const result = useUI(defaultConfig);
      const iconAttrs = result.iconAttrs;

      expect(iconAttrs.value).toHaveProperty("config");
    });
  });

  describe("Edge Cases", () => {
    it("should handle empty config", () => {
      const result = useUI({});

      expect(result.config).toBeDefined();
      expect(result.getDataTest).toBeDefined();
    });

    it("should handle null/undefined config values", () => {
      const defaultConfig = {
        body: "",
        icon: undefined,
      };

      const result = useUI(defaultConfig);

      expect(result).toHaveProperty("bodyAttrs");
      expect(result).toHaveProperty("iconAttrs");

      // Should not throw errors when accessing attributes
      expect(() => result.bodyAttrs.value).not.toThrow();
      expect(() => result.iconAttrs.value).not.toThrow();
    });

    it("should handle unstyled mode", () => {
      uiUtils.vuelessConfig.unstyled = true;

      const defaultConfig = {
        body: { base: "styled-class" },
      };

      useUI(defaultConfig);

      expect(uiUtils.getMergedConfig).toHaveBeenCalledWith(
        expect.objectContaining({
          unstyled: true,
        }),
      );
    });

    it("should handle config with only defaults", () => {
      const defaultConfig = {
        defaults: {
          variant: "primary",
          size: "md",
        },
      };

      const result = useUI(defaultConfig);

      expect(result.config).toBeDefined();
    });

    it("should handle deep config changes", async () => {
      const component = mount(TestComponent);

      // Change nested config
      await component.setProps({
        config: {
          body: {
            base: "new-base",
            variants: {
              variant: {
                custom: "custom-class",
              },
            },
          },
        },
      });

      await nextTick();

      expect(uiUtils.getMergedConfig).toHaveBeenCalled();
    });
  });

  describe("Integration Tests", () => {
    it("should work with real component mounting", () => {
      const component = mount(TestComponent);

      expect(component.exists()).toBe(true);
      expect(component.attributes("data-test")).toBe("test");
    });

    it("should handle prop changes in mounted component", async () => {
      const component = mount(TestComponent);

      // The component should render with the default dataTest from the mock
      expect(component.exists()).toBe(true);

      // Test that the component can handle config changes
      await component.setProps({
        config: {
          body: { base: "new-config-class" },
        },
      });

      // The component should still exist and function after prop changes
      expect(component.exists()).toBe(true);
    });
  });
});
