import { PageVisitCategory, UserEventCategory } from '@spiffy-ai/commerce-api-client';
import { act, render, screen, waitFor } from '@testing-library/react';
import { Provider, useAtom } from 'jotai';
import React from 'react';
import CommerceApiClient from 'src/application/commerce-api';
import Logger from 'src/application/logging/logger';
import { VariantTypeEnum } from 'src/application/models';
import {
  UrlResolverResponse,
  pageUserEventAtom,
  pageVariantInfoAtom,
  urlResolverAtom,
} from 'src/atoms/app/variant';
import { mapUrlResolverResponseToVariantInfo } from '../mapping';
import { PageProvider, usePage } from '../pageContext';

// Mock the Logger to avoid console output in tests
vi.spyOn(Logger.prototype, 'logInfo').mockImplementation(() => {});
vi.spyOn(Logger.prototype, 'logWarn').mockImplementation(() => {});
vi.spyOn(Logger.prototype, 'logError').mockImplementation(() => {});

vi.mock('src/contexts/amplitudeContext', () => ({
  useAmplitude: () => ({
    trackEvent: vi.fn(),
    isReady: true,
  }),
  EnviveMetricsEventName: {
    PageViewed: 'Page Viewed',
  },
}));

// Mock CommerceApiClient
const mockResolveUrl = vi.fn();
vi.mock('src/application/commerce-api', () => ({
  default: {
    resolveUrl: vi.fn(),
  },
}));

// Mock useAmplitude (PageProvider dispatches page viewed event internally)
vi.mock('src/contexts/amplitudeContext', () => ({
  EnviveMetricsEventName: { PageViewed: 'Page Viewed' },
  useAmplitude: () => ({ trackEvent: vi.fn() }),
}));

// Component that uses the usePage hook
const MockComponent: React.FC = () => {
  const context = usePage();

  return (
    <div data-testid="mock-component">
      <div data-testid="page-url">{context.pageUrl || 'undefined'}</div>
      <div data-testid="variant-info">
        {context.variantInfo ? JSON.stringify(context.variantInfo) : 'undefined'}
      </div>
      <div data-testid="user-event">
        {context.userEvent ? JSON.stringify(context.userEvent) : 'undefined'}
      </div>
      <div data-testid="is-loading">{context.isLoading ? 'true' : 'false'}</div>
    </div>
  );
};

// Component that directly reads from the urlResolverAtom to verify it's set
const AtomReaderComponent: React.FC = () => {
  const [urlResolverResponse] = useAtom(urlResolverAtom);
  const cacheString = JSON.stringify(urlResolverResponse);

  return <div data-testid="atom-reader">{cacheString}</div>;
};

describe('PageProvider', () => {
  const originalLocation = window.location;

  beforeEach(() => {
    vi.clearAllMocks();
    mockResolveUrl.mockClear();
    (CommerceApiClient.resolveUrl as ReturnType<typeof vi.fn>).mockImplementation(mockResolveUrl);

    // Reset window.location
    delete (window as { location?: Location }).location;
    // @ts-expect-error - window.location is a string & Location
    window.location = {
      ...originalLocation,
      href: 'https://example.com/test-page',
    } as unknown as Location;
  });

  afterEach(() => {
    // @ts-expect-error - window.location is a string & Location
    window.location = originalLocation;
  });

  const renderWithProviders = (children: React.ReactNode, previewMode = false) => {
    return render(
      <Provider>
        <PageProvider previewMode={previewMode}>{children}</PageProvider>
      </Provider>,
    );
  };

  describe('PageProvider Initialization', () => {
    it('should initialize with pageUrl from window.location.href', async () => {
      window.location.href = 'https://example.com/initial-page';

      renderWithProviders(<MockComponent />);

      await waitFor(() => {
        expect(screen.getByTestId('page-url').textContent).toBe('https://example.com/initial-page');
      });
    });

    it('should handle missing window.location gracefully', async () => {
      const originalLocation = window.location;

      // Temporarily remove location
      delete (window as { location?: Location }).location;

      // Restore it immediately since the component needs it
      // @ts-expect-error - window.location is a string & Location
      window.location = originalLocation;

      renderWithProviders(<MockComponent />);

      // Should render successfully
      expect(screen.getByTestId('mock-component')).toBeInTheDocument();
    });
  });

  describe('Preview Mode', () => {
    it('should auto-initialize atoms when previewMode is true', async () => {
      window.location.href = 'https://example.com/preview-page';

      renderWithProviders(<MockComponent />, true);

      await waitFor(() => {
        const userEvent = screen.getByTestId('user-event').textContent;
        expect(userEvent).toContain('preview-user-event');
        expect(userEvent).toContain(UserEventCategory.PageVisit);
        expect(userEvent).toContain(PageVisitCategory.Other);
      });

      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain(VariantTypeEnum.PageVisit);
        expect(variantInfo).toContain('https://example.com/preview-page');
      });
    });

    it('should skip API call when previewMode is true', async () => {
      window.location.href = 'https://example.com/preview-test';

      renderWithProviders(<MockComponent />, true);

      // Wait for initialization
      await waitFor(() => {
        expect(screen.getByTestId('user-event').textContent).not.toBe('undefined');
      });

      // Should not call resolveUrl when in preview mode
      expect(mockResolveUrl).not.toHaveBeenCalled();
    });

    it('should not overwrite existing atoms in preview mode', async () => {
      window.location.href = 'https://example.com/existing-atoms';
      // Set up existing atoms before preview mode
      const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
        const [, setUserEvent] = useAtom(pageUserEventAtom);
        const [, setVariantInfo] = useAtom(pageVariantInfoAtom);
        React.useEffect(() => {
          setUserEvent({
            event_id: 'existing-event',
            created_at: new Date().toISOString(),
            category: UserEventCategory.PageVisit,
            attributes: {
              page_visit_category: PageVisitCategory.Other,
              url: 'https://example.com/existing',
            },
          } as any);
          setVariantInfo({
            variantType: VariantTypeEnum.Pdp,
            productId: 'existing-product',
            url: 'https://example.com/existing',
          } as any);
        }, [setUserEvent, setVariantInfo]);
        return <PageProvider previewMode>{children}</PageProvider>;
      };
      render(
        <Provider>
          <TestWrapper>
            <MockComponent />
          </TestWrapper>
        </Provider>,
      );
      // Should keep existing atoms, not overwrite with preview values
      await waitFor(() => {
        const userEvent = screen.getByTestId('user-event').textContent;
        expect(userEvent).toContain('existing-event');
        expect(userEvent).not.toContain('preview-user-event');
      });
    });

    it('should set isLoading to false in preview mode', async () => {
      window.location.href = 'https://example.com/loading-test';

      renderWithProviders(<MockComponent />, true);

      await waitFor(() => {
        expect(screen.getByTestId('is-loading').textContent).toBe('false');
      });
    });
  });

  describe('URL Resolution (Non-Preview Mode)', () => {
    it('should call CommerceApiClient.resolveUrl when pageUrl is set', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);

      window.location.href = 'https://example.com/test';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        expect(mockResolveUrl).toHaveBeenCalledWith('https://example.com/test');
      });
    });

    it('should normalize URL to lowercase and trim before resolving', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);

      window.location.href = '  HTTPS://EXAMPLE.COM/TEST  ';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        expect(mockResolveUrl).toHaveBeenCalledWith('https://example.com/test');
      });
    });

    it('should use cached response from urlResolverAtom if available', async () => {
      const cachedResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.Pdp,
        specific_details: {
          pdp_attributes: {
            product_id: 'cached-product-123',
          },
        },
        ready: true,
      };

      window.location.href = 'https://example.com/cached-page';

      // Set up the atom with cached data before rendering
      const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
        const [, setUrlResolver] = useAtom(urlResolverAtom);

        React.useEffect(() => {
          setUrlResolver({
            url: 'https://example.com/cached-page',
            response: cachedResponse,
          });
        }, [setUrlResolver]);

        return <PageProvider previewMode={false}>{children}</PageProvider>;
      };

      render(
        <Provider>
          <TestWrapper>
            <MockComponent />
          </TestWrapper>
        </Provider>,
      );

      // Should not call resolveUrl since it's cached
      await waitFor(() => {
        expect(mockResolveUrl).not.toHaveBeenCalled();
      });

      // Should use cached variant info
      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain('cached-product-123');
        expect(variantInfo).toContain(VariantTypeEnum.Pdp);
      });
    });

    it('should not resolve URL when pageUrl is empty string', async () => {
      // Test with an empty URL string
      window.location.href = '';

      renderWithProviders(<MockComponent />, false);

      // Wait for initial render
      await waitFor(() => {
        expect(screen.getByTestId('mock-component')).toBeInTheDocument();
      });

      // Empty string is falsy, so resolveUrl should not be called
      await act(async () => {
        await new Promise<void>(resolve => {
          setTimeout(resolve, 100);
        });
      });

      expect(mockResolveUrl).not.toHaveBeenCalled();
    });
  });

  describe('Variant Info Mapping', () => {
    it('should map PDP variant response correctly', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.Pdp,
        specific_details: {
          pdp_attributes: {
            product_id: 'product-123',
          },
        },
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/product';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain('product-123');
        expect(variantInfo).toContain(VariantTypeEnum.Pdp);
        expect(variantInfo).toContain('https://example.com/product');
      });
    });

    it('should map PLP variant response correctly', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.Plp,
        specific_details: {
          plp_attributes: {
            attributes: {
              id: 'plp-456',
            },
          },
        },
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/category';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain('plp-456');
        expect(variantInfo).toContain(VariantTypeEnum.Plp);
        expect(variantInfo).toContain('https://example.com/category');
      });
    });

    it('should map PageVisit variant response correctly', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/page';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain(VariantTypeEnum.PageVisit);
        expect(variantInfo).toContain('https://example.com/page');
      });
    });

    it('should handle PDP variant with missing product_id', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.Pdp,
        specific_details: {
          pdp_attributes: {},
        },
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/product';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain(VariantTypeEnum.Pdp);
        expect(variantInfo).toContain('""'); // Empty string for productId
      });
    });

    it('should handle PLP variant with missing plp_id', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.Plp,
        specific_details: {
          plp_attributes: {},
        },
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/category';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const variantInfo = screen.getByTestId('variant-info').textContent;
        expect(variantInfo).toContain(VariantTypeEnum.Plp);
        expect(variantInfo).toContain('""'); // Empty string for plpId
      });
    });
  });

  describe('User Event Handling', () => {
    it('should set userEvent when provided in response', async () => {
      const mockUserEvent = {
        event_id: 'event-123',
        event_type: PageVisitCategory.Other,
        created_at: new Date().toISOString(),
        category: UserEventCategory.PageVisit,
        attributes: {
          url: 'https://example.com/page',
          page_visit_category: PageVisitCategory.Other,
        },
      };

      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
        user_event: mockUserEvent,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/page';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const userEvent = screen.getByTestId('user-event').textContent;
        expect(userEvent).toContain('event-123');
        expect(userEvent).toContain(PageVisitCategory.Other);
      });
    });

    it('should set userEvent to undefined when not provided in response', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/page';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        const userEvent = screen.getByTestId('user-event').textContent;
        expect(userEvent).toBe('undefined');
      });
    });
  });

  describe('Error Handling', () => {
    it('should log error and not crash when resolveUrl fails', async () => {
      const errorSpy = vi.spyOn(Logger.prototype, 'logError');
      const error = new Error('Network error');
      mockResolveUrl.mockRejectedValue(error);

      window.location.href = 'https://example.com/error-page';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        expect(errorSpy).toHaveBeenCalledWith('Failed to resolve page URL', error, {
          pageUrl: 'https://example.com/error-page',
        });
      });

      // Component should still render
      expect(screen.getByTestId('mock-component')).toBeInTheDocument();
    });

    it('should handle invalid variant type gracefully', async () => {
      const errorSpy = vi.spyOn(Logger.prototype, 'logError');

      const invalidResponse = {
        variant_type: 'invalid_type',
        specific_details: {},
        ready: true,
      } as unknown as UrlResolverResponse;

      mockResolveUrl.mockResolvedValue(invalidResponse);
      window.location.href = 'https://example.com/invalid';

      renderWithProviders(<MockComponent />, false);

      // The mapping function should throw, which will be caught in the useEffect
      await waitFor(() => {
        expect(errorSpy).toHaveBeenCalled();
      });
    });
  });

  describe('usePage Hook', () => {
    it('should throw error when used outside of PageProvider', () => {
      // Suppress console.error for this test
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

      expect(() => {
        render(
          <Provider>
            <MockComponent />
          </Provider>,
        );
      }).toThrow('usePage must be used within a PageProvider');

      consoleSpy.mockRestore();
    });

    it('should return context values when used within PageProvider', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/test';

      renderWithProviders(<MockComponent />, false);

      await waitFor(() => {
        expect(screen.getByTestId('page-url').textContent).toBe('https://example.com/test');
        expect(screen.getByTestId('variant-info').textContent).not.toBe('undefined');
      });
    });
  });

  describe('Context Value Memoization', () => {
    it('should memoize context value to prevent unnecessary re-renders', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.PageVisit,
        specific_details: {},
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/memo-test';

      let renderCount = 0;
      const CountingComponent: React.FC = () => {
        renderCount += 1;
        const context = usePage();
        return <div data-testid="count">{renderCount}</div>;
      };

      renderWithProviders(<CountingComponent />, false);

      await waitFor(() => {
        expect(screen.getByTestId('count')).toBeInTheDocument();
      });

      // The component should render, and the context value should be stable
      const initialCount = renderCount;

      // Trigger a re-render (simulating parent re-render)
      await act(async () => {
        await new Promise<void>(resolve => {
          setTimeout(resolve, 100);
        });
      });

      // The context value should be memoized
      expect(renderCount).toBeGreaterThanOrEqual(initialCount);
    });
  });

  describe('Integration with urlResolverAtom', () => {
    it('should update urlResolverAtom when resolving new URL', async () => {
      const mockResponse: UrlResolverResponse = {
        variant_type: VariantTypeEnum.Pdp,
        specific_details: {
          pdp_attributes: {
            product_id: 'product-999',
          },
        },
        ready: true,
      };

      mockResolveUrl.mockResolvedValue(mockResponse);
      window.location.href = 'https://example.com/new-page';

      render(
        <Provider>
          <PageProvider previewMode={false}>
            <MockComponent />
            <AtomReaderComponent />
          </PageProvider>
        </Provider>,
      );

      await waitFor(() => {
        expect(mockResolveUrl).toHaveBeenCalled();
      });
    });
  });
});

describe('mapUrlResolverResponseToVariantInfo', () => {
  it('should map PDP variant correctly', () => {
    const response: UrlResolverResponse = {
      variant_type: VariantTypeEnum.Pdp,
      specific_details: {
        pdp_attributes: {
          product_id: 'test-product-123',
        },
        collections: ['test-collection-123'],
        number_of_reviews: 10,
        merchant_tags: ['test-merchant-tag-123'],
      },
      ready: true,
    };

    const result = mapUrlResolverResponseToVariantInfo('https://example.com/product', response);

    expect(result).toEqual({
      variantType: VariantTypeEnum.Pdp,
      productId: 'test-product-123',
      collections: ['test-collection-123'],
      numberOfReviews: 10,
      merchantTags: ['test-merchant-tag-123'],
      url: 'https://example.com/product',
    });
  });

  it('should map PLP variant correctly', () => {
    const response: UrlResolverResponse = {
      variant_type: VariantTypeEnum.Plp,
      specific_details: {
        top_category: 'test-top-category',
        plp_attributes: {
          attributes: {
            id: 'test-plp-456',
          },
        },
      },
      ready: true,
    };

    const result = mapUrlResolverResponseToVariantInfo('https://example.com/category', response);

    expect(result).toEqual({
      variantType: VariantTypeEnum.Plp,
      plpId: 'test-plp-456',
      topCategory: 'test-top-category',
      url: 'https://example.com/category',
    });
  });

  it('should map PageVisit variant correctly', () => {
    const response: UrlResolverResponse = {
      variant_type: VariantTypeEnum.PageVisit,
      specific_details: {},
      ready: true,
    };

    const result = mapUrlResolverResponseToVariantInfo('https://example.com/page', response);

    expect(result).toEqual({
      variantType: VariantTypeEnum.PageVisit,
      url: 'https://example.com/page',
    });
  });

  it('should handle PDP with missing product_id', () => {
    const response: UrlResolverResponse = {
      variant_type: VariantTypeEnum.Pdp,
      specific_details: {
        pdp_attributes: {},
      },
      ready: true,
    };

    const result = mapUrlResolverResponseToVariantInfo('https://example.com/product', response);

    expect(result).toEqual({
      variantType: VariantTypeEnum.Pdp,
      productId: '',
      collections: [],
      numberOfReviews: 0,
      merchantTags: [],
      url: 'https://example.com/product',
    });
  });

  it('should handle PLP with missing plp_id', () => {
    const response: UrlResolverResponse = {
      variant_type: VariantTypeEnum.Plp,
      specific_details: {
        plp_attributes: {},
      },
      ready: true,
    };

    const result = mapUrlResolverResponseToVariantInfo('https://example.com/category', response);

    expect(result).toEqual({
      variantType: VariantTypeEnum.Plp,
      plpId: '',
      topCategory: '',
      url: 'https://example.com/category',
    });
  });

  it('should throw error for invalid variant type', () => {
    const response = {
      variant_type: 'invalid_type',
      specific_details: {},
      ready: true,
    } as unknown as UrlResolverResponse;

    expect(() => {
      mapUrlResolverResponseToVariantInfo('https://example.com/page', response);
    }).toThrow('Invalid variant type');
  });
});
