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

import React, { createRef, forwardRef } from "react";
import { NextImage } from "./index";

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

// Mock next/image - must use require-style reference due to hoisting
const mockNextImage = jest.fn();

jest.mock("next/image", () => {
  const MockNextImage = forwardRef(function MockNextImage(
    props: any,
    ref: any
  ) {
    mockNextImage(props);
    const rest = { ...props };
    const loader = rest.loader;
    delete rest.loader;
    delete rest.unoptimized;
    delete rest.priority;
    delete rest.fill;
    delete rest.sizes;
    return (
      <img
        ref={ref}
        data-testid="next-image"
        data-unoptimized={props.unoptimized?.toString() || "false"}
        data-has-loader={loader ? "true" : "false"}
        {...rest}
      />
    );
  });
  return { __esModule: true, default: MockNextImage };
});

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

describe("NextImage", () => {
  beforeEach(() => {
    mockNextImage.mockClear();
  });

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

  describe("basic rendering", () => {
    it("renders an image element", () => {
      render(<NextImage src="/test.png" alt="Test" width={100} height={100} />);
      expect(screen.getByTestId("next-image")).toBeInTheDocument();
    });

    it("passes src, alt, width, height props", () => {
      render(
        <NextImage src="/photo.jpg" alt="Photo" width={200} height={150} />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("src", "/photo.jpg");
      expect(img).toHaveAttribute("alt", "Photo");
      expect(img).toHaveAttribute("width", "200");
      expect(img).toHaveAttribute("height", "150");
    });

    it("passes className to image", () => {
      render(
        <NextImage
          src="/img.png"
          alt="Img"
          width={50}
          height={50}
          className="rounded-lg"
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveClass("rounded-lg");
    });

    it("passes additional props through", () => {
      render(
        <NextImage
          src="/img.png"
          alt="Img"
          width={50}
          height={50}
          priority={true}
        />
      );
      expect(mockNextImage).toHaveBeenCalledWith(
        expect.objectContaining({ priority: true })
      );
    });
  });

  describe("ref forwarding", () => {
    it("forwards ref to the underlying image", () => {
      const ref = createRef<HTMLImageElement>();
      render(
        <NextImage ref={ref} src="/img.png" alt="Ref" width={50} height={50} />
      );
      expect(ref.current).toBeInstanceOf(HTMLImageElement);
    });
  });

  describe("Contentful image detection", () => {
    it("detects Contentful images by domain", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/abc/image.png"
          alt="Contentful"
          width={100}
          height={100}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-has-loader", "true");
      expect(img).toHaveAttribute("data-unoptimized", "false");
    });

    it("does not use contentful loader for non-Contentful images", () => {
      render(
        <NextImage
          src="https://example.com/photo.jpg"
          alt="External"
          width={100}
          height={100}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-has-loader", "false");
    });

    it("does not use contentful loader for relative paths", () => {
      render(
        <NextImage
          src="/local/image.png"
          alt="Local"
          width={100}
          height={100}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-has-loader", "false");
    });

    it("handles non-string src gracefully", () => {
      const staticImport = { src: "/static.png", height: 100, width: 100 };
      render(
        <NextImage
          src={staticImport as any}
          alt="Static"
          width={100}
          height={100}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-has-loader", "false");
    });
  });

  describe("custom loader behavior", () => {
    it("passes a loader function for Contentful images", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/image.jpg"
          alt="CF"
          width={100}
          height={100}
        />
      );
      expect(mockNextImage).toHaveBeenCalledWith(
        expect.objectContaining({
          loader: expect.any(Function),
          unoptimized: false,
        })
      );
    });

    it("loader appends width, quality, and format params", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/photo.jpg"
          alt="CF"
          width={100}
          height={100}
        />
      );
      const callProps = mockNextImage.mock.calls[0][0];
      const loader = callProps.loader;
      const result = loader({
        src: "https://images.ctfassets.net/space/photo.jpg",
        width: 800,
        quality: 75,
      });
      expect(result).toContain("w=800");
      expect(result).toContain("q=75");
      expect(result).toContain("fm=webp");
    });

    it("loader uses default quality of 90 when not specified", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/photo.jpg"
          alt="CF"
          width={100}
          height={100}
        />
      );
      const callProps = mockNextImage.mock.calls[0][0];
      const loader = callProps.loader;
      const result = loader({
        src: "https://images.ctfassets.net/space/photo.jpg",
        width: 400,
        quality: undefined,
      });
      expect(result).toContain("q=90");
    });

    it("loader preserves existing URL params", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/photo.jpg?fit=fill"
          alt="CF"
          width={100}
          height={100}
        />
      );
      const callProps = mockNextImage.mock.calls[0][0];
      const loader = callProps.loader;
      const result = loader({
        src: "https://images.ctfassets.net/space/photo.jpg?fit=fill",
        width: 600,
        quality: 80,
      });
      expect(result).toContain("fit=fill");
      expect(result).toContain("w=600");
      expect(result).toContain("q=80");
      expect(result).toContain("fm=webp");
    });
  });

  describe("SVG optimization handling", () => {
    it("marks SVG from Contentful as unoptimized", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/logo.svg"
          alt="SVG"
          width={100}
          height={100}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-unoptimized", "true");
      expect(img).toHaveAttribute("data-has-loader", "false");
    });

    it("does not use contentful loader for SVG images", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/icon.svg"
          alt="SVG Icon"
          width={24}
          height={24}
        />
      );
      expect(mockNextImage).toHaveBeenCalledWith(
        expect.objectContaining({
          unoptimized: true,
        })
      );
      expect(mockNextImage).not.toHaveBeenCalledWith(
        expect.objectContaining({
          loader: expect.any(Function),
        })
      );
    });

    it("handles SVG with query params from Contentful", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/icon.svg?v=1"
          alt="SVG"
          width={50}
          height={50}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-unoptimized", "true");
      expect(img).toHaveAttribute("data-has-loader", "false");
    });

    it("handles SVG with uppercase extension from Contentful", () => {
      render(
        <NextImage
          src="https://images.ctfassets.net/space/Logo.SVG"
          alt="SVG"
          width={100}
          height={100}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-unoptimized", "true");
    });

    it("does not treat non-Contentful SVG specially", () => {
      render(
        <NextImage
          src="https://example.com/icon.svg"
          alt="Ext SVG"
          width={24}
          height={24}
        />
      );
      const img = screen.getByTestId("next-image");
      // Non-contentful SVG: no loader, unoptimized is false (falsy)
      expect(img).toHaveAttribute("data-has-loader", "false");
      expect(img).toHaveAttribute("data-unoptimized", "false");
    });
  });

  describe("non-Contentful images", () => {
    it("does not set unoptimized for regular external images", () => {
      render(
        <NextImage
          src="https://cdn.example.com/photo.webp"
          alt="WebP"
          width={400}
          height={300}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-has-loader", "false");
    });

    it("does not set unoptimized for local images", () => {
      render(
        <NextImage
          src="/images/hero.png"
          alt="Hero"
          width={1200}
          height={600}
        />
      );
      const img = screen.getByTestId("next-image");
      expect(img).toHaveAttribute("data-unoptimized");
    });
  });
});

describe("resolveDefaultExport (CJS/ESM interop)", () => {
  afterEach(() => {
    jest.resetModules();
  });

  it("unwraps doubly-wrapped default export", async () => {
    jest.doMock("next/image", () => {
      function MockImage(props: any) {
        return <img {...props} />;
      }
      return { __esModule: true, default: { default: MockImage } };
    });
    jest.doMock("@shared/utils", () => ({
      cx: (...args: any[]) => args.filter(Boolean).join(" "),
    }));
    const { NextImage } = await import("./index");
    expect(NextImage).toBeDefined();
  });

  it("resolves when default export is already a function", async () => {
    jest.doMock("next/image", () => {
      function MockImage(props: any) {
        return <img {...props} />;
      }
      return { __esModule: true, default: MockImage };
    });
    jest.doMock("@shared/utils", () => ({
      cx: (...args: any[]) => args.filter(Boolean).join(" "),
    }));
    const { NextImage } = await import("./index");
    expect(NextImage).toBeDefined();
  });

  it("returns current when loop exhausts iterations", async () => {
    jest.doMock("next/image", () => {
      const nested = {
        default: {
          default: {
            default: {
              default: {
                default: { default: "not-a-function" },
              },
            },
          },
        },
      };
      return { __esModule: true, default: nested };
    });
    jest.doMock("@shared/utils", () => ({
      cx: (...args: any[]) => args.filter(Boolean).join(" "),
    }));
    const { NextImage } = await import("./index");
    expect(NextImage).toBeDefined();
  });
});
