import "@testing-library/jest-dom";

import { createRef } from "react";
import { Input, InputField } from "./index";

import { fireEvent, render, screen } from "@testing-library/react";

// Mock dependencies
jest.mock("@shared/components/material-icon", () => ({
  MaterialIcon: ({ name, size, className }: any) => (
    <span data-testid={`icon-${name}`} data-size={size} className={className}>
      {name}
    </span>
  ),
}));

jest.mock("@shared/components/text", () => ({
  Text: ({ children, className, as }: any) => {
    const Tag = as || "span";
    return (
      <Tag data-testid="text" className={className}>
        {children}
      </Tag>
    );
  },
}));

jest.mock("@shared/utils", () => ({
  cx: (...args: any[]) => args.filter(Boolean).join(" "),
}));

describe("Input", () => {
  it("has displayName set to Input", () => {
    expect(Input.displayName).toBe("Input");
  });

  describe("basic rendering", () => {
    it("renders an input element", () => {
      render(<Input />);
      expect(screen.getByRole("textbox")).toBeInTheDocument();
    });

    it("renders with placeholder", () => {
      render(<Input placeholder="Enter text" />);
      expect(screen.getByPlaceholderText("Enter text")).toBeInTheDocument();
    });

    it("renders with default type text", () => {
      render(<Input data-testid="input" />);
      const input = screen.getByTestId("input");
      expect(input).toHaveAttribute("type", "text");
    });

    it("renders with custom type", () => {
      render(<Input type="email" data-testid="input" />);
      const input = screen.getByTestId("input");
      expect(input).toHaveAttribute("type", "email");
    });
  });

  describe("ref forwarding", () => {
    it("forwards ref to input element", () => {
      const ref = createRef<HTMLInputElement>();
      render(<Input ref={ref} />);
      expect(ref.current).toBeInstanceOf(HTMLInputElement);
    });

    it("can imperatively focus the input via ref", () => {
      const ref = createRef<HTMLInputElement>();
      render(<Input ref={ref} />);
      ref.current?.focus();
      expect(ref.current).toBe(document.activeElement);
    });
  });

  describe("label", () => {
    it("renders label when provided", () => {
      render(<Input label="Email" />);
      expect(screen.getByText("Email")).toBeInTheDocument();
    });

    it("does not render label when not provided", () => {
      const { container } = render(<Input />);
      expect(container.querySelector("label")).not.toBeInTheDocument();
    });

    it("associates label with input via htmlFor/id", () => {
      render(<Input label="Username" />);
      const input = screen.getByRole("textbox");
      expect(input).toHaveAttribute("id", "Username");
    });

    it("shows required asterisk when required is true", () => {
      render(<Input label="Email" required={true} />);
      expect(screen.getByText("*")).toBeInTheDocument();
    });

    it("does not show asterisk when required is false", () => {
      render(<Input label="Email" />);
      expect(screen.queryByText("*")).not.toBeInTheDocument();
    });
  });

  describe("size variants", () => {
    it("applies medium size class by default", () => {
      const { container } = render(<Input />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("h-14");
    });

    it("applies slim size class", () => {
      const { container } = render(<Input size="slim" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("h-12");
    });

    it("applies large size class", () => {
      const { container } = render(<Input size="large" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("h-[60px]");
    });
  });

  describe("state management", () => {
    it("applies hover state on mouseOver", () => {
      render(<Input data-testid="input" />);
      const input = screen.getByTestId("input");
      fireEvent.mouseOver(input);
      const wrapper = input.closest(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-hover");
    });

    it("removes hover state on mouseOut", () => {
      render(<Input data-testid="input" />);
      const input = screen.getByTestId("input");
      fireEvent.mouseOver(input);
      fireEvent.mouseOut(input);
      const wrapper = input.closest(".relative.flex");
      expect(wrapper?.className).not.toContain("border-input-border-hover");
    });

    it("applies focus state on focus", () => {
      render(<Input data-testid="input" />);
      const input = screen.getByTestId("input");
      fireEvent.focus(input);
      const wrapper = input.closest(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-selected");
    });

    it("removes focus state on blur", () => {
      render(<Input data-testid="input" />);
      const input = screen.getByTestId("input");
      fireEvent.focus(input);
      fireEvent.blur(input);
      const wrapper = input.closest(".relative.flex");
      expect(wrapper?.className).not.toContain("border-input-border-selected");
    });

    it("calls onFocus callback", () => {
      const onFocus = jest.fn();
      render(<Input data-testid="input" onFocus={onFocus} />);
      fireEvent.focus(screen.getByTestId("input"));
      expect(onFocus).toHaveBeenCalled();
    });

    it("calls onBlur callback", () => {
      const onBlur = jest.fn();
      render(<Input data-testid="input" onBlur={onBlur} />);
      fireEvent.blur(screen.getByTestId("input"));
      expect(onBlur).toHaveBeenCalled();
    });
  });

  describe("error state", () => {
    it("applies error state styling when state is error", () => {
      const { container } = render(<Input state="error" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-critical");
    });

    it("displays error text when state is error and errorText provided", () => {
      render(<Input state="error" errorText="Invalid input" />);
      expect(screen.getByText("Invalid input")).toBeInTheDocument();
    });

    it("does not display error text when state is not error", () => {
      render(<Input state="default" errorText="Invalid input" />);
      expect(screen.queryByText("Invalid input")).not.toBeInTheDocument();
    });

    it("does not display error section when errorText is empty", () => {
      render(<Input state="error" errorText="" />);
      expect(screen.queryByTestId("icon-info")).not.toBeInTheDocument();
    });

    it("applies error state when hasError is true", () => {
      const { container } = render(<Input hasError={true} />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-critical");
    });

    it("hasError overrides state prop", () => {
      const { container } = render(<Input hasError={true} state="default" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-critical");
    });

    it("renders info icon with error text", () => {
      render(<Input state="error" errorText="Error msg" />);
      expect(screen.getByTestId("icon-info")).toBeInTheDocument();
    });
  });

  describe("prefix icon", () => {
    it("renders prefix icon when prefixIconName is provided", () => {
      render(<Input prefixIconName="search" />);
      expect(screen.getByTestId("icon-search")).toBeInTheDocument();
    });

    it("does not render prefix icon when not provided", () => {
      render(<Input />);
      expect(screen.queryByTestId("icon-search")).not.toBeInTheDocument();
      expect(screen.queryByTestId("icon-location_on")).not.toBeInTheDocument();
    });

    it("applies custom prefix icon size", () => {
      render(<Input prefixIconName="search" prefixIconSize={40} />);
      const icon = screen.getByTestId("icon-search");
      expect(icon).toHaveAttribute("data-size", "40");
    });

    it("applies prefixIconClassName", () => {
      render(
        <Input prefixIconName="search" prefixIconClassName="text-gray-500" />
      );
      const icon = screen.getByTestId("icon-search");
      expect(icon).toHaveClass("text-gray-500");
    });
  });

  describe("suffix icon and password toggle", () => {
    it("renders suffix icon when suffixIconName is provided", () => {
      render(<Input suffixIconName="visibility" />);
      // Default inputType is "text", so icon shows "visibility"
      expect(screen.getByTestId("icon-visibility")).toBeInTheDocument();
    });

    it("toggles password visibility on suffix icon click", () => {
      render(
        <Input
          type="password"
          suffixIconName="visibility"
          data-testid="input"
        />
      );
      const input = screen.getByTestId("input");
      expect(input).toHaveAttribute("type", "password");

      // When type is password, icon shows visibility_off
      const iconWrapper = screen.getByTestId(
        "icon-visibility_off"
      ).parentElement;
      fireEvent.click(iconWrapper!);
      expect(input).toHaveAttribute("type", "text");

      // After toggle to text, icon shows visibility
      const visibleIcon = screen.getByTestId("icon-visibility").parentElement;
      fireEvent.click(visibleIcon!);
      expect(input).toHaveAttribute("type", "password");
    });

    it("shows visibility icon when type is text after toggle", () => {
      render(<Input type="password" suffixIconName="visibility" />);
      const iconWrapper = screen.getByTestId(
        "icon-visibility_off"
      ).parentElement;
      fireEvent.click(iconWrapper!);
      expect(screen.getByTestId("icon-visibility")).toBeInTheDocument();
    });
  });

  describe("disabled and loading", () => {
    it("disables input when disabled prop is true", () => {
      render(<Input disabled={true} data-testid="input" />);
      expect(screen.getByTestId("input")).toBeDisabled();
    });

    it("disables input when loading is true", () => {
      render(<Input loading={true} data-testid="input" />);
      expect(screen.getByTestId("input")).toBeDisabled();
    });

    it("is not disabled by default", () => {
      render(<Input data-testid="input" />);
      expect(screen.getByTestId("input")).not.toBeDisabled();
    });
  });

  describe("containerClassName", () => {
    it("applies containerClassName to the input wrapper", () => {
      const { container } = render(<Input containerClassName="custom-cnt" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("custom-cnt");
    });
  });

  describe("className", () => {
    it("applies className to the input element", () => {
      render(<Input className="custom-input" data-testid="input" />);
      const input = screen.getByTestId("input");
      expect(input).toHaveClass("custom-input");
    });
  });

  describe("state prop variants", () => {
    it("applies focus styling when state is focus", () => {
      const { container } = render(<Input state="focus" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-selected");
    });

    it("applies active styling when state is active", () => {
      const { container } = render(<Input state="active" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-selected");
    });

    it("applies hover styling when state is hover and not focused", () => {
      const { container } = render(<Input state="hover" />);
      const wrapper = container.querySelector(".relative.flex");
      expect(wrapper?.className).toContain("border-input-border-hover");
    });

    it("does not apply hover styling when state is hover but focused", () => {
      render(<Input state="hover" data-testid="input" />);
      const input = screen.getByTestId("input");
      fireEvent.focus(input);
      const wrapper = input.closest(".relative.flex");
      expect(wrapper?.className).not.toContain("border-input-border-hover");
    });
  });

  describe("InputField export", () => {
    it("exports InputField as a render function", () => {
      expect(typeof InputField).toBe("function");
    });
  });
});
