import React, { useContext } from "react";
import { View, Text } from "react-native";
import { render, fireEvent, waitFor, act } from "@testing-library/react-native";

import {
  MeasurementsPortal,
  MeasurementsPortalContextProvider,
  MeasurementPortalContext,
} from "../MeasurementsPortal";

// Mock uuid
jest.mock("uuid", () => ({
  v4: jest.fn(() => "mock-uuid-key"),
}));

describe("MeasurementsPortal", () => {
  const mockOnLayout = jest.fn();
  const MockComponent = jest.fn(() => <View testID="mock-component" />);

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it("renders with correct testID", () => {
    const { getByTestId } = render(
      <MeasurementsPortal Component={MockComponent} onLayout={mockOnLayout} />
    );

    expect(getByTestId("MeasurementsPortal")).toBeTruthy();
  });

  it("renders the passed Component", () => {
    const { getByTestId } = render(
      <MeasurementsPortal Component={MockComponent} onLayout={mockOnLayout} />
    );

    expect(getByTestId("mock-component")).toBeTruthy();
    expect(MockComponent).toHaveBeenCalled();
  });

  it("passes componentProps to the Component", () => {
    const componentProps = {
      testProp: "test-value",
      anotherProp: 123,
    };

    render(
      <MeasurementsPortal
        Component={MockComponent}
        onLayout={mockOnLayout}
        componentProps={componentProps}
      />
    );

    expect(MockComponent).toHaveBeenCalledWith(componentProps, {});
  });

  it("calls onLayout when layout changes", () => {
    const { getByTestId } = render(
      <MeasurementsPortal Component={MockComponent} onLayout={mockOnLayout} />
    );

    const container = getByTestId("MeasurementsPortal");
    const wrapper = container.children[0] as any;

    const layoutEvent = {
      nativeEvent: {
        layout: { width: 100, height: 200 },
      },
    };

    fireEvent(wrapper, "layout", layoutEvent);

    expect(mockOnLayout).toHaveBeenCalledWith(layoutEvent);
  });

  it("applies correct container styles", () => {
    const { getByTestId } = render(
      <MeasurementsPortal Component={MockComponent} onLayout={mockOnLayout} />
    );

    const container = getByTestId("MeasurementsPortal");

    expect(container.props.style).toEqual({
      position: "absolute",
      opacity: 0,
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
    });
  });

  it("renders wrapper with onLayout", () => {
    const { getByTestId } = render(
      <MeasurementsPortal Component={MockComponent} onLayout={mockOnLayout} />
    );

    const container = getByTestId("MeasurementsPortal");
    const wrapper = container.children[0] as any;

    expect(wrapper.props.onLayout).toBe(mockOnLayout);
  });

  it("works with different Component types", () => {
    const CustomComponent = ({ title }: { title: string }) => (
      <Text testID="custom-text">{title}</Text>
    );

    const props = { title: "Test Title" };

    const { getByTestId, getByText } = render(
      <MeasurementsPortal
        Component={CustomComponent}
        onLayout={mockOnLayout}
        componentProps={props}
      />
    );

    expect(getByTestId("custom-text")).toBeTruthy();
    expect(getByText("Test Title")).toBeTruthy();
  });

  it("handles component without componentProps", () => {
    render(
      <MeasurementsPortal Component={MockComponent} onLayout={mockOnLayout} />
    );

    expect(MockComponent).toHaveBeenCalledWith({}, {});
  });
});

describe("MeasurementsPortalContextProvider", () => {
  const TestConsumer = () => {
    const context = useContext(MeasurementPortalContext);

    return (
      <View testID="test-consumer">
        <Text testID="measuring-status">
          {context?.measuringInProgress ? "measuring" : "idle"}
        </Text>
      </View>
    );
  };

  it("provides context to children", () => {
    const { getByTestId } = render(
      <MeasurementsPortalContextProvider>
        <TestConsumer />
      </MeasurementsPortalContextProvider>
    );

    expect(getByTestId("test-consumer")).toBeTruthy();
    expect(getByTestId("measuring-status")).toBeTruthy();
  });

  it("initially sets measuringInProgress to false", () => {
    const { getByText } = render(
      <MeasurementsPortalContextProvider>
        <TestConsumer />
      </MeasurementsPortalContextProvider>
    );

    expect(getByText("idle")).toBeTruthy();
  });

  it("renders MeasurementsPortal with default View component", () => {
    const { getByTestId } = render(
      <MeasurementsPortalContextProvider>
        <View />
      </MeasurementsPortalContextProvider>
    );

    expect(getByTestId("MeasurementsPortal")).toBeTruthy();
  });

  describe("measureComponent function", () => {
    const TestMeasureComponent = () => {
      const context = useContext(MeasurementPortalContext);
      const [result, setResult] = React.useState<any>(null);

      const handleMeasure = async () => {
        if (context?.measureComponent) {
          const measurement = await context.measureComponent(View, {
            index: 0,
            onLoadFinished: () => {},
          });

          setResult(measurement);
        }
      };

      return (
        <View testID="measure-component">
          <Text testID="measuring-status">
            {context?.measuringInProgress ? "measuring" : "idle"}
          </Text>
          <Text testID="measure-button" onPress={handleMeasure}>
            Measure
          </Text>
          {result ? (
            <Text testID="result">
              {result.width}x{result.height}
            </Text>
          ) : null}
        </View>
      );
    };

    it("sets measuringInProgress to true when measureComponent is called", async () => {
      const { getByTestId, getByText } = render(
        <MeasurementsPortalContextProvider>
          <TestMeasureComponent />
        </MeasurementsPortalContextProvider>
      );

      expect(getByText("idle")).toBeTruthy();

      await act(async () => {
        fireEvent.press(getByTestId("measure-button"));
      });

      expect(getByText("measuring")).toBeTruthy();
    });

    it("can measure a component through the context", async () => {
      const TestMeasureComponent = () => {
        const context = useContext(MeasurementPortalContext);
        const [measured, setMeasured] = React.useState(false);

        React.useEffect(() => {
          if (context?.measureComponent) {
            context
              .measureComponent(View, {
                index: 5,
                onLoadFinished: () => {},
              })
              .then(() => {
                setMeasured(true);
              });
          }
        }, [context]);

        return (
          <View testID="measure-component">
            <Text testID="measure-status">
              {measured ? "measured" : "not-measured"}
            </Text>
          </View>
        );
      };

      const { getByText } = render(
        <MeasurementsPortalContextProvider>
          <TestMeasureComponent />
        </MeasurementsPortalContextProvider>
      );

      // Initially should be not measured
      expect(getByText("not-measured")).toBeTruthy();
    });

    it("handles error in onLoadFinished", async () => {
      const ErrorComponent = ({ onLoadFinished }: any) => {
        React.useEffect(() => {
          // Simulate error
          setTimeout(
            () => onLoadFinished({ error: new Error("Test error") }),
            10
          );
        }, [onLoadFinished]);

        return <View testID="error-component" />;
      };

      const TestErrorFlow = () => {
        const context = useContext(MeasurementPortalContext);
        const [result, setResult] = React.useState<any>(null);

        const handleMeasure = React.useCallback(async () => {
          if (context?.measureComponent) {
            const measurement = await context.measureComponent(ErrorComponent, {
              index: 1,
              onLoadFinished: () => {},
            });

            setResult(measurement);
          }
        }, [context]);

        React.useEffect(() => {
          handleMeasure();
        }, [handleMeasure]);

        return (
          <View testID="error-flow">
            {result ? (
              <Text testID="error-result">
                {result.width}x{result.height}
              </Text>
            ) : null}
          </View>
        );
      };

      const { queryByText } = render(
        <MeasurementsPortalContextProvider>
          <TestErrorFlow />
        </MeasurementsPortalContextProvider>
      );

      // Wait for error handling
      await waitFor(() => {
        expect(queryByText("0x0")).toBeTruthy();
      });
    });
  });
});

describe("MeasurementPortalContext", () => {
  it("returns null when used outside of provider", () => {
    const TestComponent = () => {
      const context = useContext(MeasurementPortalContext);

      return (
        <Text testID="context-value">
          {context ? "has-context" : "no-context"}
        </Text>
      );
    };

    const { getByText } = render(<TestComponent />);
    expect(getByText("no-context")).toBeTruthy();
  });

  it("returns context value when used inside provider", () => {
    const TestComponent = () => {
      const context = useContext(MeasurementPortalContext);

      return (
        <Text testID="context-value">
          {context ? "has-context" : "no-context"}
        </Text>
      );
    };

    const { getByText } = render(
      <MeasurementsPortalContextProvider>
        <TestComponent />
      </MeasurementsPortalContextProvider>
    );

    expect(getByText("has-context")).toBeTruthy();
  });
});
