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

import UNumber from "../UNumber.vue";
import { MATH_SIGN, MATH_SIGN_TYPE } from "../utilNumber";

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

describe("UNumber.vue", () => {
  let value: number;

  beforeAll(() => {
    value = 1234.56;
  });

  describe("Props", () => {
    it("Size – applies the correct size class", async () => {
      const sizeClasses = {
        xs: "text-tiny",
        sm: "text-small",
        md: "text-medium",
        lg: "text-large",
      };

      Object.entries(sizeClasses).forEach(([size, classes]) => {
        const component = mount(UNumber, {
          props: {
            size: size as Props["size"],
          },
        });

        expect(component.attributes("class")).toContain(classes);
      });
    });

    it("Color – applies the correct color class", async () => {
      const colors = [
        "primary",
        "secondary",
        "error",
        "warning",
        "success",
        "info",
        "notice",
        "neutral",
        "grayscale",
      ];

      colors.forEach((color) => {
        const component = mount(UNumber, {
          props: {
            color: color as Props["color"],
          },
        });

        expect(component.attributes("class")).toContain(color);
      });
    });

    it("Value – renders the correct number value", () => {
      const expectedFormattedNumber = "1 234,56";

      const component = mount(UNumber, {
        props: {
          value,
        },
      });

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

    it("Sign – renders the correct sign based on sign prop", () => {
      const testNegativeValue = -123;

      // Auto sign (negative value)
      // Should show minus sign for negative values
      const autoComponent = mount(UNumber, {
        props: {
          value: testNegativeValue,
          sign: MATH_SIGN_TYPE.auto as Props["sign"],
        },
      });

      expect(autoComponent.text()).toContain(MATH_SIGN.MINUS);

      // Auto sign (positive value)
      // Should not show any sign for positive values
      const autoPositiveComponent = mount(UNumber, {
        props: {
          value,
          sign: MATH_SIGN_TYPE.auto as Props["sign"],
        },
      });

      expect(autoPositiveComponent.text()).not.toContain(MATH_SIGN.MINUS);
      expect(autoPositiveComponent.text()).not.toContain(MATH_SIGN.PLUS);

      // Positive sign
      // Should show plus sign regardless of value
      const positiveComponent = mount(UNumber, {
        props: {
          value,
          sign: MATH_SIGN_TYPE.positive as Props["sign"],
        },
      });

      expect(positiveComponent.text()).toContain(MATH_SIGN.PLUS);

      // Negative sign
      // Should show minus sign regardless of value
      const negativeComponent = mount(UNumber, {
        props: {
          value,
          sign: MATH_SIGN_TYPE.negative as Props["sign"],
        },
      });

      expect(negativeComponent.text()).toContain(MATH_SIGN.MINUS);

      // Unsigned
      // Should not show any sign regardless of value
      const unsignedComponent = mount(UNumber, {
        props: {
          value: testNegativeValue,
          sign: MATH_SIGN_TYPE.unsigned as Props["sign"],
        },
      });

      expect(unsignedComponent.text()).not.toContain(MATH_SIGN.MINUS);
      expect(unsignedComponent.text()).not.toContain(MATH_SIGN.PLUS);
    });

    it("Currency – renders the currency symbol", () => {
      const currency = "$";

      const component = mount(UNumber, {
        props: {
          value,
          currency,
        },
      });

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

    it("CurrencyAlign – aligns currency correctly based on currencyAlign prop", () => {
      const currency = "$";

      const alignTests = [
        { align: "left", expectation: (text: string) => text.startsWith(currency) },
        { align: "right", expectation: (text: string) => text.endsWith(currency) },
      ];

      alignTests.forEach(({ align, expectation }) => {
        const component = mount(UNumber, {
          props: {
            value,
            currency,
            currencyAlign: align as Props["currencyAlign"],
          },
        });

        expect(expectation(component.text())).toBe(true);
      });
    });

    it("CurrencySpace – adds space between currency and number when currencySpace is true", () => {
      const currency = "$";

      const spaceTests = [
        { space: true, align: "left" },
        { space: false, align: "left" },
        { space: true, align: "right" },
        { space: false, align: "right" },
      ];

      spaceTests.forEach(({ space, align }) => {
        const component = mount(UNumber, {
          props: {
            value,
            currency,
            currencySpace: space,
            currencyAlign: align as Props["currencyAlign"],
          },
        });

        expect(component.html()).toContain(currency);
        // Visual inspection of spacing would be done in a real browser
        // Here we just verify the component renders with the given props
        expect(component.props().currencySpace).toBe(space);
        expect(component.props().currencyAlign).toBe(align);
      });
    });

    it("Raw – renders formatted number as plain text without HTML elements when raw is true", () => {
      const component = mount(UNumber, {
        props: {
          value,
          raw: true,
        },
      });

      // Should render plain text without the number div structure
      expect(component.find("[vl-key='number']").exists()).toBe(false);
      expect(component.text()).toContain("1 234,56");
    });

    it("Raw – renders with currency when raw is true and currency is set", () => {
      const currency = "$";

      const component = mount(UNumber, {
        props: {
          value,
          currency,
          currencyAlign: "left",
          raw: true,
        },
      });

      expect(component.text()).toBe("$1 234,56");
    });

    it("Raw – renders with currency and space when raw is true, currency is set, and currencySpace is true", () => {
      const currency = "$";

      const component = mount(UNumber, {
        props: {
          value,
          currency,
          currencyAlign: "left",
          currencySpace: true,
          raw: true,
        },
      });

      expect(component.text()).toBe("$ 1 234,56");
    });

    it("Raw – renders with currency on right when raw is true and currencyAlign is right", () => {
      const currency = "$";

      const component = mount(UNumber, {
        props: {
          value,
          currency,
          currencyAlign: "right",
          raw: true,
        },
      });

      expect(component.text()).toBe("1 234,56$");
    });

    // eslint-disable-next-line vue/max-len
    it("Raw – renders with currency on right and space when raw is true, currencyAlign is right, and currencySpace is true", () => {
      const currency = "$";

      const component = mount(UNumber, {
        props: {
          value,
          currency,
          currencyAlign: "right",
          currencySpace: true,
          raw: true,
        },
      });

      expect(component.text()).toBe("1 234,56 $");
    });

    it("Raw – renders with sign when raw is true and sign is set", () => {
      const testNegativeValue = -123;

      const component = mount(UNumber, {
        props: {
          value: testNegativeValue,
          sign: MATH_SIGN_TYPE.auto as Props["sign"],
          raw: true,
        },
      });

      expect(component.text()).toContain(MATH_SIGN.MINUS);
    });

    it("MinFractionDigits – adds zeros to meet the minimum fraction digits requirement", () => {
      const value = 123;
      const minFractionDigits = 2;
      const expectedMinFractionResult = "123,00";

      const component = mount(UNumber, {
        props: {
          value,
          minFractionDigits,
        },
      });

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

    it("MaxFractionDigits – rounds the fraction to the maximum number of digits", () => {
      const value = 123.456789;
      const maxFractionDigits = 2;
      const expectedMaxFractionResult = "123,46"; // Rounded from .456789 to 2 digits

      const component = mount(UNumber, {
        props: {
          value,
          maxFractionDigits,
        },
      });

      expect(component.text()).toContain(expectedMaxFractionResult);
      // Original decimal part should not be present
      const originalDecimalPart = value.toString().split(".")[1];

      expect(component.text()).not.toContain(originalDecimalPart);
    });

    it("DecimalSeparator – uses the correct decimal separator", () => {
      const value = 123.45;
      const decimalSeparator = ",";
      const expectedFormattedNumber = "123,45";

      const component = mount(UNumber, {
        props: {
          value,
          decimalSeparator,
        },
      });

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

    it("ThousandsSeparator – uses the correct thousands separator", () => {
      const value = 1234567.89;
      const thousandsSeparator = " ";
      const expectedFormattedInteger = "1 234 567";

      const component = mount(UNumber, {
        props: {
          value,
          thousandsSeparator,
        },
      });

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

    it("Align – applies the correct align class", () => {
      const alignClasses = {
        left: "justify-start",
        right: "justify-end",
      };

      Object.entries(alignClasses).forEach(([align, classes]) => {
        const component = mount(UNumber, {
          props: {
            align: align as Props["align"],
          },
        });

        expect(component.attributes("class")).toContain(classes);
      });
    });

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

      const component = mount(UNumber, {
        props: {
          dataTest: testDataTest,
        },
      });

      expect(component.find("[vl-key='number']").attributes("data-test")).toBe(testDataTest);
    });
  });

  describe("Slots", () => {
    it("Left – renders content from left slot", () => {
      const slotText = "Left";
      const slotClass = "left-content";

      const component = mount(UNumber, {
        props: {
          value,
        },
        slots: {
          left: `<span class='${slotClass}'>${slotText}</span>`,
        },
      });

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

    it("Right – renders content from right slot", () => {
      const slotText = "Right";
      const slotClass = "right-content";

      const component = mount(UNumber, {
        props: {
          value,
        },
        slots: {
          right: `<span class='${slotClass}'>${slotText}</span>`,
        },
      });

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

  describe("Exposed refs", () => {
    it("wrapperRef – exposes wrapperRef", () => {
      const component = mount(UNumber, {});

      expect(component.vm.wrapperRef).toBeDefined();
    });
  });
});
