// @vitest-environment jsdom
import { renderHook } from '@testing-library/react';
import { useContext } from 'react';
import { describe, expect, it } from 'vitest';

import {
  TelemetryContext,
  TelemetryContextProvider,
  useTelemetryAncestorData,
  useTelemetryData,
} from './TelemetryContext';

describe('TelemetryContextProvider', () => {
  it('should provide telemetry context data to children', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        recommendationItemId='item-456'
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'run-123',
        recommendationItemId: 'item-456',
      },
      parent: undefined,
    });
  });

  it('should provide partial telemetry context data', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider recommendationRunId='run-123'>
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'run-123',
      },
      parent: undefined,
    });
  });

  it('should support nested providers without merge', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        recommendationItemId='outer-item'
      >
        <TelemetryContextProvider
          recommendationRunId='inner-run'
          recommendationItemId='inner-item'
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'inner-run',
        recommendationItemId: 'inner-item',
      },
      parent: {
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: 'outer-item',
        },
      },
    });
  });

  it('should merge parent data when `merge` prop is enabled', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        recommendationItemId='outer-item'
      >
        <TelemetryContextProvider
          recommendationItemId='inner-item'
          merge={true}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'outer-run',
        recommendationItemId: 'inner-item',
      },
      parent: {
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: 'outer-item',
        },
      },
    });
  });

  it('should prefer child values over parent when merge=true and child has explicit values', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        recommendationItemId='outer-item'
      >
        <TelemetryContextProvider
          recommendationRunId='inner-run'
          recommendationItemId='inner-item'
          merge={true}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'inner-run',
        recommendationItemId: 'inner-item',
      },
      parent: {
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: 'outer-item',
        },
      },
    });
  });

  it('should include id in context value when provided', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider id='context-1' recommendationRunId='run-123'>
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      id: 'context-1',
      data: {
        recommendationRunId: 'run-123',
      },
    });
  });

  it('should support nested providers with different ids', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        id='outer-context'
        recommendationRunId='outer-run'
      >
        <TelemetryContextProvider
          id='inner-context'
          recommendationItemId='inner-item'
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      id: 'inner-context',
      data: {
        recommendationItemId: 'inner-item',
      },
      parent: {
        id: 'outer-context',
        data: {
          recommendationRunId: 'outer-run',
        },
      },
    });
  });

  it('should include clientData in context when provided', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        clientData={{ source: 'homepage', userId: 42, isActive: true }}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'run-123',
        clientData: {
          source: 'homepage',
          userId: 42,
          isActive: true,
        },
      },
    });
  });

  it('should merge clientData when merge=true', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        clientData={{ source: 'outer', count: 10 }}
      >
        <TelemetryContextProvider
          recommendationItemId='inner-item'
          clientData={{ source: 'inner', newProp: 'value' }}
          merge={true}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    // Child's clientData should take precedence when merge=true
    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'outer-run',
        recommendationItemId: 'inner-item',
        clientData: {
          source: 'inner',
          newProp: 'value',
        },
      },
    });
  });

  it('should support clientData with null values', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        clientData={{ property: null }}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      data: {
        recommendationRunId: 'run-123',
        clientData: {
          property: null,
        },
      },
    });
  });

  it('should support both id and clientData together', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        id='context-with-client-data'
        recommendationRunId='run-123'
        clientData={{ source: 'test', version: 2 }}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useContext(TelemetryContext), {
      wrapper,
    });

    expect(result.current).toMatchObject({
      id: 'context-with-client-data',
      data: {
        recommendationRunId: 'run-123',
        clientData: {
          source: 'test',
          version: 2,
        },
      },
    });
  });

  it('should memoize clientData deeply and not change context reference for equivalent objects', () => {
    let clientData = { source: 'test', count: 1 };

    const Wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        clientData={clientData}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result, rerender } = renderHook(
      () => useContext(TelemetryContext),
      {
        wrapper: Wrapper,
      }
    );

    const initialData = result.current?.data;

    // Rerender with a new object that has the same deep content
    clientData = { source: 'test', count: 1 };
    rerender();

    // The data object reference should be stable due to useDeepCompareMemo
    expect(result.current?.data).toBe(initialData);
    expect(result.current?.data.clientData).toEqual({
      source: 'test',
      count: 1,
    });

    // Now rerender with actually different data
    clientData = { source: 'test', count: 2 };
    rerender();

    // The data object reference should change when content actually changes
    expect(result.current?.data).not.toBe(initialData);
    expect(result.current?.data.clientData).toEqual({
      source: 'test',
      count: 2,
    });
  });

  it('should not rerender children when context value reference stays stable', () => {
    function useTestHook() {
      const context = useContext(TelemetryContext);
      return context?.data;
    }

    // Use state to track which clientData to use
    let clientDataProp = { source: 'test', count: 1 };

    const Wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        clientData={clientDataProp}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result, rerender } = renderHook(() => useTestHook(), {
      wrapper: Wrapper,
    });

    const initialData = result.current;

    // Rerender with a new object that has the same deep content
    clientDataProp = { source: 'test', count: 1 };
    rerender();
    expect(result.current).toBe(initialData);

    // Now rerender with data that is actually different
    clientDataProp = { source: 'test', count: 2 };
    rerender();

    // The data reference should change when content actually changes
    expect(result.current).not.toBe(initialData);
    expect(result.current?.clientData).toEqual({
      source: 'test',
      count: 2,
    });
  });
});

describe('useTelemetryData', () => {
  it('should return undefined when no provider exists', () => {
    const { result } = renderHook(() => useTelemetryData());

    expect(result.current).toBeUndefined();
  });

  it('should return telemetry data without parent reference', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider recommendationRunId='run-123'>
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryData(), { wrapper });

    expect(result.current).toEqual({
      recommendationRunId: 'run-123',
      recommendationItemId: undefined,
    });
    expect(result.current).not.toHaveProperty('parent');
  });

  it('should return current level data only in nested providers', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider recommendationRunId='outer-run'>
        <TelemetryContextProvider recommendationItemId='inner-item'>
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryData(), { wrapper });

    // Should only return the innermost provider's data
    expect(result.current).toEqual({
      recommendationRunId: undefined,
      recommendationItemId: 'inner-item',
    });
  });

  it('should return clientData when provided', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        clientData={{ source: 'feed', page: 1 }}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryData(), { wrapper });

    expect(result.current).toEqual({
      recommendationRunId: 'run-123',
      recommendationItemId: undefined,
      clientData: {
        source: 'feed',
        page: 1,
      },
    });
  });

  it('should return merged clientData when merge=true', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        clientData={{ source: 'outer', count: 5 }}
      >
        <TelemetryContextProvider
          recommendationItemId='inner-item'
          clientData={{ page: 2 }}
          merge={true}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryData(), { wrapper });

    // Should have merged data with child's clientData taking precedence
    expect(result.current).toEqual({
      recommendationRunId: 'outer-run',
      recommendationItemId: 'inner-item',
      clientData: {
        page: 2,
      },
    });
  });
});

describe('useTelemetryAncestorData', () => {
  it('should return empty array when no provider exists', () => {
    const { result } = renderHook(() => useTelemetryAncestorData());

    expect(result.current).toEqual([]);
  });

  it('should return single level data in flat array', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        id='test-id'
        recommendationRunId='run-123'
        recommendationItemId='item-456'
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    expect(result.current).toEqual([
      {
        id: 'test-id',
        data: {
          recommendationRunId: 'run-123',
          recommendationItemId: 'item-456',
        },
      },
    ]);
  });

  it('should return all ancestor data from nested providers', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider id='outer-id' recommendationRunId='outer-run'>
        <TelemetryContextProvider
          id='inner-id'
          recommendationItemId='inner-item'
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    // Should return array from innermost to outermost
    expect(result.current).toEqual([
      {
        id: 'inner-id',
        data: {
          recommendationRunId: undefined,
          recommendationItemId: 'inner-item',
        },
      },
      {
        id: 'outer-id',
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: undefined,
        },
      },
    ]);
  });

  it('should return all ancestor data with merge enabled', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider id='test-id' recommendationRunId='outer-run'>
        <TelemetryContextProvider
          recommendationItemId='inner-item'
          merge={true}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    // First element should have merged data, second should be original parent
    expect(result.current).toEqual([
      {
        data: {
          recommendationRunId: 'outer-run', // merged from parent
          recommendationItemId: 'inner-item',
        },
      },
      {
        id: 'test-id',
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: undefined,
        },
      },
    ]);
  });

  it('should handle deeply nested providers', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider recommendationRunId='level-1'>
        <TelemetryContextProvider recommendationItemId='level-2'>
          <TelemetryContextProvider recommendationItemId='level-3'>
            {children}
          </TelemetryContextProvider>
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    expect(result.current).toHaveLength(3);
    expect(result.current[0]).toEqual({
      data: { recommendationRunId: undefined, recommendationItemId: 'level-3' },
    });
    expect(result.current[1]).toEqual({
      data: { recommendationRunId: undefined, recommendationItemId: 'level-2' },
    });
    expect(result.current[2]).toEqual({
      data: { recommendationRunId: 'level-1', recommendationItemId: undefined },
    });
  });

  it('should include clientData in ancestor data array', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='run-123'
        clientData={{ source: 'homepage', version: 1 }}
      >
        {children}
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    expect(result.current).toEqual([
      {
        data: {
          recommendationRunId: 'run-123',
          recommendationItemId: undefined,
          clientData: {
            source: 'homepage',
            version: 1,
          },
        },
      },
    ]);
  });

  it('should include clientData from all nested providers', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        clientData={{ source: 'outer', level: 1 }}
      >
        <TelemetryContextProvider
          recommendationItemId='inner-item'
          clientData={{ source: 'inner', level: 2 }}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    expect(result.current).toEqual([
      {
        data: {
          recommendationRunId: undefined,
          recommendationItemId: 'inner-item',
          clientData: {
            source: 'inner',
            level: 2,
          },
        },
      },
      {
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: undefined,
          clientData: {
            source: 'outer',
            level: 1,
          },
        },
      },
    ]);
  });

  it('should include merged clientData in ancestor data when merge=true', () => {
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <TelemetryContextProvider
        recommendationRunId='outer-run'
        clientData={{ source: 'outer', count: 10 }}
      >
        <TelemetryContextProvider
          recommendationItemId='inner-item'
          clientData={{ page: 2 }}
          merge={true}
        >
          {children}
        </TelemetryContextProvider>
      </TelemetryContextProvider>
    );

    const { result } = renderHook(() => useTelemetryAncestorData(), {
      wrapper,
    });

    // First element should have merged data, second should be original parent
    expect(result.current).toEqual([
      {
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: 'inner-item',
          clientData: {
            page: 2,
          },
        },
      },
      {
        data: {
          recommendationRunId: 'outer-run',
          recommendationItemId: undefined,
          clientData: {
            source: 'outer',
            count: 10,
          },
        },
      },
    ]);
  });
});
