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

import React from "react";
import CookieBanner from "./index";

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

// Mock cookie utilities
const mockGetCookie = jest.fn();
const mockSetCookie = jest.fn();

jest.mock("../../../utils/cookie", () => ({
  getCookie: (...args: any[]) => mockGetCookie(...args),
  setCookie: (...args: any[]) => mockSetCookie(...args),
}));

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

jest.mock("@shared/contentful/blocks/button", () => ({
  Button: (props: any) => (
    <button onClick={props.onClick} data-testid="cookie-button">
      {props.children}
    </button>
  ),
}));

describe("CookieBanner", () => {
  const defaultContent = {
    entryName: "Cookie Notice",
    anchorId: "cookie-banner",
    title: "Cookies",
    richText: <p>We use cookies to improve your experience.</p>,
  };

  beforeEach(() => {
    jest.useFakeTimers();
    mockGetCookie.mockReset();
    mockSetCookie.mockReset();
    // Default: cookie not set (banner should show)
    mockGetCookie.mockReturnValue(null);
    // Reset body classes
    document.body.classList.remove("cookie-banner-visible");
    // Reset CSS custom property
    document.documentElement.style.removeProperty("--cookie-banner-height");
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  describe("cookie management", () => {
    it("shows the banner when cookieBannerClosed cookie is not set", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      expect(
        screen.getByLabelText("Cookie usage notification")
      ).toBeInTheDocument();
    });

    it("hides the banner when cookieBannerClosed cookie is true", () => {
      mockGetCookie.mockReturnValue("true");
      const { container } = render(<CookieBanner content={defaultContent} />);
      expect(container.firstChild).toBeNull();
    });

    it("calls getCookie with cookieBannerClosed key", () => {
      render(<CookieBanner content={defaultContent} />);
      expect(mockGetCookie).toHaveBeenCalledWith("cookieBannerClosed");
    });

    it("sets cookieBannerClosed cookie on close", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      fireEvent.click(screen.getByTestId("cookie-button"));
      expect(mockSetCookie).toHaveBeenCalledWith(
        "cookieBannerClosed",
        true,
        expect.objectContaining({ expires: expect.any(Date) })
      );
    });

    it("sets cookie with correct expiration (30 days)", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);

      const now = Date.now();
      fireEvent.click(screen.getByTestId("cookie-button"));

      const callArgs = mockSetCookie.mock.calls[0];
      const expirationDate = callArgs[2].expires;
      // 43200 minutes * 60 * 1000 = 30 days in milliseconds
      const expectedMs = 43200 * 60 * 1000;
      const diff = expirationDate.getTime() - now;
      expect(diff).toBeGreaterThanOrEqual(expectedMs - 1000);
      expect(diff).toBeLessThanOrEqual(expectedMs + 1000);
    });
  });

  describe("rendering", () => {
    it("renders with fixed positioning", () => {
      render(<CookieBanner content={defaultContent} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      expect(banner.className).toContain("fixed");
    });

    it("renders the rich text content", () => {
      render(<CookieBanner content={defaultContent} />);
      expect(
        screen.getByText("We use cookies to improve your experience.")
      ).toBeInTheDocument();
    });

    it("does not render rich text when richText is falsy", () => {
      const content = { ...defaultContent, richText: null as any };
      render(<CookieBanner content={content} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      // Banner is shown but no rich text inside
      expect(banner.querySelector(".mx-auto")).toBeNull();
    });

    it("renders close button with close icon", () => {
      render(<CookieBanner content={defaultContent} />);
      expect(screen.getByTestId("icon-close")).toBeInTheDocument();
    });

    it("has correct z-index class", () => {
      render(<CookieBanner content={defaultContent} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      expect(banner.className).toContain("z-[1000]");
    });

    it("renders with correct max width", () => {
      render(<CookieBanner content={defaultContent} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      expect(banner.className).toContain("max-w-[350px]");
    });

    it("renders with correct positioning styles", () => {
      render(<CookieBanner content={defaultContent} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      expect(banner.style.bottom).toBe("0px");
      expect(banner.style.right).toBe("20px");
    });

    it("has id cookie-banner", () => {
      render(<CookieBanner content={defaultContent} />);
      expect(document.getElementById("cookie-banner")).toBeInTheDocument();
    });
  });

  describe("close behavior", () => {
    it("hides banner on close button click", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      expect(
        screen.getByLabelText("Cookie usage notification")
      ).toBeInTheDocument();
      fireEvent.click(screen.getByTestId("cookie-button"));
      expect(
        screen.queryByLabelText("Cookie usage notification")
      ).not.toBeInTheDocument();
    });
  });

  describe("body class management", () => {
    it("adds cookie-banner-visible class to body when visible", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      expect(document.body.classList.contains("cookie-banner-visible")).toBe(
        true
      );
    });

    it("removes cookie-banner-visible class when banner is hidden", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      fireEvent.click(screen.getByTestId("cookie-button"));
      expect(document.body.classList.contains("cookie-banner-visible")).toBe(
        false
      );
    });

    it("removes cookie-banner-visible class on unmount", () => {
      mockGetCookie.mockReturnValue(null);
      const { unmount } = render(<CookieBanner content={defaultContent} />);
      expect(document.body.classList.contains("cookie-banner-visible")).toBe(
        true
      );
      unmount();
      expect(document.body.classList.contains("cookie-banner-visible")).toBe(
        false
      );
    });
  });

  describe("sticky footer detection", () => {
    it("uses larger margin when anchored-banner element exists", () => {
      const anchoredBanner = document.createElement("div");
      anchoredBanner.id = "anchored-banner";
      document.body.appendChild(anchoredBanner);

      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      // marginBottom = 14 * 4 = 56px when anchored-banner exists
      expect(banner.style.marginBottom).toBe("56px");

      document.body.removeChild(anchoredBanner);
    });

    it("uses smaller margin when anchored-banner does not exist", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);
      const banner = screen.getByLabelText("Cookie usage notification");
      // marginBottom = 3 * 4 = 12px when no anchored-banner
      expect(banner.style.marginBottom).toBe("12px");
    });
  });

  describe("CSS custom property for height", () => {
    it("calculates and sets --cookie-banner-height property", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);

      act(() => {
        jest.advanceTimersByTime(350);
      });

      // The property should be set (value depends on getBoundingClientRect mock)
      // In jsdom, getBoundingClientRect returns zeros, so spaceFromBottom = innerHeight - 0
      const value = document.documentElement.style.getPropertyValue(
        "--cookie-banner-height"
      );
      // Should be set (even though value may be 0px in jsdom)
      expect(value).toBeDefined();
    });

    it("removes --cookie-banner-height on unmount", () => {
      mockGetCookie.mockReturnValue(null);
      const { unmount } = render(<CookieBanner content={defaultContent} />);

      act(() => {
        jest.advanceTimersByTime(350);
      });

      unmount();
      const value = document.documentElement.style.getPropertyValue(
        "--cookie-banner-height"
      );
      expect(value).toBe("");
    });

    it("recalculates height on window resize", () => {
      mockGetCookie.mockReturnValue(null);
      render(<CookieBanner content={defaultContent} />);

      act(() => {
        jest.advanceTimersByTime(350);
      });

      act(() => {
        window.dispatchEvent(new Event("resize"));
        jest.advanceTimersByTime(200);
      });

      // Should not throw and property remains set
      const value = document.documentElement.style.getPropertyValue(
        "--cookie-banner-height"
      );
      expect(value).toBeDefined();
    });
  });
});
