import React from "react";
import { AnimationWrapper } from "./index";

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

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

// Mock framer-motion
const mockUseReducedMotion = jest.fn(() => false);

jest.mock("framer-motion", () => {
  const actual = jest.requireActual("framer-motion");
  return {
    ...actual,
    useReducedMotion: () => mockUseReducedMotion(),
    motion: new Proxy(
      {},
      {
        get: (_target, prop: string) => {
          const motionKeys = [
            "whileHover",
            "whileTap",
            "transition",
            "animate",
            "initial",
            "exit",
            "variants",
          ];
          const MotionMock = React.forwardRef(
            (props: Record<string, unknown>, ref: React.Ref<unknown>) => {
              const rest: Record<string, unknown> = {};
              Object.keys(props).forEach(key => {
                if (!motionKeys.includes(key)) {
                  rest[key] = props[key];
                }
              });
              return React.createElement(prop, {
                ...rest,
                ref,
                "data-motion": "true",
                "data-while-hover": JSON.stringify(props.whileHover),
                "data-while-tap": JSON.stringify(props.whileTap),
                "data-transition": JSON.stringify(props.transition),
              });
            }
          );
          MotionMock.displayName = `Motion(${prop})`;
          return MotionMock;
        },
      }
    ),
  };
});

describe("AnimationWrapper", () => {
  beforeEach(() => {
    mockUseReducedMotion.mockReturnValue(false);
  });

  describe("when animation is disabled", () => {
    it("returns the child directly when disableAnimation is true (default)", () => {
      render(
        <AnimationWrapper>
          <button data-testid="child">Click</button>
        </AnimationWrapper>
      );
      const btn = screen.getByTestId("child");
      expect(btn).toBeInTheDocument();
      expect(btn.tagName).toBe("BUTTON");
      expect(btn).not.toHaveAttribute("data-motion");
    });

    it("returns the child directly when disableAnimation is explicitly true", () => {
      render(
        <AnimationWrapper disableAnimation={true}>
          <span data-testid="child">Text</span>
        </AnimationWrapper>
      );
      expect(screen.getByTestId("child")).not.toHaveAttribute("data-motion");
    });

    it("returns the child directly when user prefers reduced motion", () => {
      mockUseReducedMotion.mockReturnValue(true);
      render(
        <AnimationWrapper disableAnimation={false} animationType="scale">
          <div data-testid="child">Content</div>
        </AnimationWrapper>
      );
      expect(screen.getByTestId("child")).not.toHaveAttribute("data-motion");
    });
  });

  describe("when animation is enabled", () => {
    describe("with intrinsic elements", () => {
      it("wraps intrinsic element with motion component for scale animation", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="scale">
            <div data-testid="child">Animated</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        expect(el).toHaveAttribute("data-motion", "true");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({ scale: 1.02 });
        const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
        expect(whileTap).toEqual({ scale: 0.98 });
      });

      it("applies shadow animation preset", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="shadow">
            <div data-testid="child">Shadow</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({
          boxShadow: "0 10px 25px rgba(0,0,0,0.15)",
        });
        // shadow preset has no whileTap, so mergedWhileTap is undefined
        const whileTap = el.getAttribute("data-while-tap");
        expect(whileTap).toBeNull();
      });

      it("applies lift animation preset", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="lift">
            <div data-testid="child">Lift</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({ y: -5 });
        const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
        expect(whileTap).toEqual({ y: 0 });
      });

      it("applies opacity animation preset", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="opacity">
            <div data-testid="child">Opacity</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({ opacity: 0.8 });
        const transition = JSON.parse(
          el.getAttribute("data-transition") || "{}"
        );
        expect(transition).toEqual({ duration: 0.2 });
      });

      it("applies grow animation preset", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="grow">
            <div data-testid="child">Grow</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({ scale: 1.1 });
        const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
        expect(whileTap).toEqual({ scale: 0.95 });
        const transition = JSON.parse(
          el.getAttribute("data-transition") || "{}"
        );
        expect(transition).toEqual({
          type: "spring",
          stiffness: 300,
          damping: 20,
        });
      });

      it("works with different intrinsic HTML elements (span)", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="scale">
            <span data-testid="child">Span</span>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        expect(el.tagName).toBe("SPAN");
        expect(el).toHaveAttribute("data-motion", "true");
      });

      it("works with anchor elements", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType="lift">
            <a data-testid="child" href="/test">
              Link
            </a>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        expect(el.tagName).toBe("A");
        expect(el).toHaveAttribute("href", "/test");
        expect(el).toHaveAttribute("data-motion", "true");
      });
    });

    describe("with composite (non-intrinsic) elements", () => {
      const CustomComponent = ({ label }: { label: string }) => (
        <span data-testid="custom">{label}</span>
      );

      it("wraps composite component in motion.div", () => {
        const { container } = render(
          <AnimationWrapper disableAnimation={false} animationType="scale">
            <CustomComponent label="Hello" />
          </AnimationWrapper>
        );
        // The wrapper should be a div with motion attributes
        const wrapper = container.firstElementChild;
        expect(wrapper?.tagName).toBe("DIV");
        expect(wrapper).toHaveAttribute("data-motion", "true");
        // The child should be rendered inside
        expect(screen.getByTestId("custom")).toBeInTheDocument();
        expect(screen.getByText("Hello")).toBeInTheDocument();
      });

      it("applies animation presets to motion.div wrapper", () => {
        const { container } = render(
          <AnimationWrapper disableAnimation={false} animationType="grow">
            <CustomComponent label="Test" />
          </AnimationWrapper>
        );
        const wrapper = container.firstElementChild;
        const whileHover = JSON.parse(
          wrapper?.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({ scale: 1.1 });
      });
    });

    describe("with multiple animation types", () => {
      it("merges multiple animation presets", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            animationType={["scale", "shadow"]}
          >
            <div data-testid="child">Multi</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({
          scale: 1.02,
          boxShadow: "0 10px 25px rgba(0,0,0,0.15)",
        });
      });

      it("merges whileTap from multiple presets", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            animationType={["scale", "lift"]}
          >
            <div data-testid="child">Multi Tap</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
        expect(whileTap).toEqual({ scale: 0.98, y: 0 });
      });

      it("merges transitions from multiple presets", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            animationType={["opacity", "lift"]}
          >
            <div data-testid="child">Merged</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const transition = JSON.parse(
          el.getAttribute("data-transition") || "{}"
        );
        expect(transition).toEqual({
          duration: 0.2,
          type: "spring",
          stiffness: 300,
          damping: 20,
        });
      });

      it("handles empty array of animation types", () => {
        render(
          <AnimationWrapper disableAnimation={false} animationType={[]}>
            <div data-testid="child">No Animation</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        // With empty array, mergePresets returns undefined, so no preset is applied
        expect(el).toHaveAttribute("data-motion", "true");
      });
    });

    describe("custom overrides", () => {
      it("overrides preset whileHover with custom prop", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            animationType="scale"
            whileHover={{ scale: 2 }}
          >
            <div data-testid="child">Override</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        // Custom override should merge with preset (custom wins)
        expect(whileHover.scale).toBe(2);
      });

      it("overrides preset whileTap with custom prop", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            animationType="scale"
            whileTap={{ scale: 0.5 }}
          >
            <div data-testid="child">Override Tap</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
        expect(whileTap.scale).toBe(0.5);
      });

      it("overrides preset transition with custom prop", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            animationType="scale"
            transition={{ duration: 1 }}
          >
            <div data-testid="child">Override Trans</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const transition = JSON.parse(
          el.getAttribute("data-transition") || "{}"
        );
        expect(transition.duration).toBe(1);
      });

      it("merges custom whileHover with the default animation preset", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            whileHover={{ rotate: 10 }}
          >
            <div data-testid="child">Custom Only</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileHover = JSON.parse(
          el.getAttribute("data-while-hover") || "{}"
        );
        expect(whileHover).toEqual({ scale: 1.05, rotate: 10 });
      });

      it("merges custom whileTap with the default animation preset", () => {
        render(
          <AnimationWrapper disableAnimation={false} whileTap={{ rotate: -5 }}>
            <div data-testid="child">Tap</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const whileTap = JSON.parse(el.getAttribute("data-while-tap") || "{}");
        expect(whileTap).toEqual({ scale: 0.95, rotate: -5 });
      });

      it("merges custom transition with the default animation preset", () => {
        render(
          <AnimationWrapper
            disableAnimation={false}
            transition={{ delay: 0.5 }}
          >
            <div data-testid="child">Trans</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        const transition = JSON.parse(
          el.getAttribute("data-transition") || "{}"
        );
        expect(transition).toEqual({ duration: 0.15, delay: 0.5 });
      });
    });

    describe("no animationType and no custom overrides", () => {
      it("renders motion component with undefined animation props", () => {
        render(
          <AnimationWrapper disableAnimation={false}>
            <div data-testid="child">Plain</div>
          </AnimationWrapper>
        );
        const el = screen.getByTestId("child");
        expect(el).toHaveAttribute("data-motion", "true");
      });
    });
  });

  describe("displayName", () => {
    it("has correct displayName", () => {
      expect(AnimationWrapper.displayName).toBe("AnimationWrapper");
    });
  });
});
