import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

const mockSigninRedirect = vi.fn();
const mockAuth = {
  user: {
    access_token: 'valid-token',
  },
  isAuthenticated: true,
  signinRedirect: mockSigninRedirect,
};

vi.mock('react-oidc-context', () => ({
  useAuth: () => mockAuth,
}));

vi.mock('framer-motion', () => ({
  motion: {
    div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
    header: ({ children, ...props }: any) => <header {...props}>{children}</header>,
    button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
  },
  AnimatePresence: ({ children }: any) => children,
}));

const originalFetch = global.fetch;

describe('UpgradePage', () => {
  let UpgradePage: typeof import('../../components/UpgradePage').UpgradePage;

  beforeEach(async () => {
    vi.clearAllMocks();
    vi.resetModules();

    mockAuth.user = { access_token: 'valid-token' };
    mockAuth.isAuthenticated = true;
    global.fetch = vi.fn();

    const module = await import('../../components/UpgradePage');
    UpgradePage = module.UpgradePage;
  });

  afterEach(() => {
    global.fetch = originalFetch;
    vi.resetAllMocks();
  });

  describe('loading state', () => {
    it('shows loader while checking status', async () => {
      let resolveStatus: (value: any) => void;
      const statusPromise = new Promise((resolve) => {
        resolveStatus = resolve;
      });

      (global.fetch as ReturnType<typeof vi.fn>).mockReturnValue(statusPromise);

      render(<UpgradePage />);

      // The loader has animate-spin class
      expect(document.querySelector('.animate-spin')).toBeInTheDocument();

      await act(async () => {
        resolveStatus!({
          ok: true,
          json: () => Promise.resolve({ hasAccess: false }),
        });
      });
    });
  });

  describe('user without access', () => {
    beforeEach(() => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({ hasAccess: false }),
      });
    });

    it('renders upgrade page with pricing', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Unlock/i)).toBeInTheDocument();
      });

      expect(screen.getByText('Monthly')).toBeInTheDocument();
      expect(screen.getByText('Yearly')).toBeInTheDocument();
      expect(screen.getByText('Starter')).toBeInTheDocument();
      expect(screen.getByText('Pro Gamer')).toBeInTheDocument();
    });

    it('shows $0 for Starter plan', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('$0')).toBeInTheDocument();
      });
    });

    it('defaults to yearly billing cycle', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('$4.17')).toBeInTheDocument();
      });

      expect(screen.getByText(/Billed \$50 yearly/i)).toBeInTheDocument();
    });

    it('switches to monthly pricing when monthly is clicked', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('$4.17')).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText('Monthly'));

      await waitFor(() => {
        expect(screen.getByText('$5')).toBeInTheDocument();
      });

      expect(screen.queryByText(/Billed \$50 yearly/i)).not.toBeInTheDocument();
    });

    it('shows feature comparison', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('5 AI generations per month')).toBeInTheDocument();
      });

      expect(screen.getByText('250 AI generations per month')).toBeInTheDocument();
      expect(screen.getByText('Basic quiz topics')).toBeInTheDocument();
      expect(screen.getByText('Host up to 10 players')).toBeInTheDocument();
      expect(screen.getByText('Host up to 100 players')).toBeInTheDocument();
    });

    it('shows back button when onBack is provided', async () => {
      const mockOnBack = vi.fn();
      render(<UpgradePage onBack={mockOnBack} />);

      await waitFor(() => {
        expect(screen.getByText(/Unlock/i)).toBeInTheDocument();
      });

      const backButton = document.querySelector('button[class*="absolute"]');
      expect(backButton).toBeInTheDocument();
    });

    it('calls onBack when back button is clicked', async () => {
      const mockOnBack = vi.fn();
      render(<UpgradePage onBack={mockOnBack} />);

      await waitFor(() => {
        expect(screen.getByText(/Unlock/i)).toBeInTheDocument();
      });

      const backButton = document.querySelector('button[class*="absolute"]');
      if (backButton) {
        fireEvent.click(backButton);
        expect(mockOnBack).toHaveBeenCalled();
      }
    });
  });

  describe('user with access', () => {
    beforeEach(() => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({ hasAccess: true }),
      });
    });

    it('shows "You\'re a Pro!" message', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText("You're a Pro!")).toBeInTheDocument();
      });
    });

    it('shows "Back to Game" button', async () => {
      const mockOnBack = vi.fn();
      render(<UpgradePage onBack={mockOnBack} />);

      await waitFor(() => {
        expect(screen.getByText('Back to Game')).toBeInTheDocument();
      });
    });

    it('calls onBack when "Back to Game" is clicked', async () => {
      const mockOnBack = vi.fn();
      render(<UpgradePage onBack={mockOnBack} />);

      await waitFor(() => {
        expect(screen.getByText('Back to Game')).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText('Back to Game'));
      expect(mockOnBack).toHaveBeenCalled();
    });
  });

  describe('checkout flow', () => {
    beforeEach(() => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ hasAccess: false }),
      });
    });

    it('calls checkout endpoint when "Upgrade Now" is clicked', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ url: 'https://checkout.stripe.com/test' }),
      });

      const originalHref = window.location.href;
      Object.defineProperty(window, 'location', {
        value: { href: '', origin: 'http://localhost' },
        writable: true,
      });

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Upgrade Now/i)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText(/Upgrade Now/i));

      await waitFor(() => {
        expect(global.fetch).toHaveBeenCalledWith(
          expect.stringContaining('/api/payments/checkout'),
          expect.objectContaining({
            method: 'POST',
            headers: expect.objectContaining({
              'Content-Type': 'application/json',
              Authorization: 'Bearer valid-token',
            }),
          })
        );
      });

      Object.defineProperty(window, 'location', {
        value: { href: originalHref },
        writable: true,
      });
    });

    it('redirects to sign in if no token', async () => {
      mockAuth.user = null;

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Upgrade Now/i)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText(/Upgrade Now/i));

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

    it('shows error message on checkout failure', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: false,
        status: 500,
      });

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Upgrade Now/i)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText(/Upgrade Now/i));

      await waitFor(() => {
        expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();
      });
    });

    it('passes correct planType for yearly billing', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ url: 'https://checkout.stripe.com/test' }),
      });

      const originalHref = window.location.href;
      Object.defineProperty(window, 'location', {
        value: { href: '', origin: 'http://localhost' },
        writable: true,
      });

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Upgrade Now/i)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText(/Upgrade Now/i));

      await waitFor(() => {
        expect(global.fetch).toHaveBeenCalledWith(
          expect.any(String),
          expect.objectContaining({
            body: expect.stringContaining('"planType":"yearly"'),
          })
        );
      });

      Object.defineProperty(window, 'location', {
        value: { href: originalHref },
        writable: true,
      });
    });

    it('passes correct planType for monthly billing', async () => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
        ok: true,
        json: () => Promise.resolve({ url: 'https://checkout.stripe.com/test' }),
      });

      const originalHref = window.location.href;
      Object.defineProperty(window, 'location', {
        value: { href: '', origin: 'http://localhost' },
        writable: true,
      });

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Upgrade Now/i)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText('Monthly'));

      await waitFor(() => {
        expect(screen.getByText('$5')).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText(/Upgrade Now/i));

      await waitFor(() => {
        expect(global.fetch).toHaveBeenCalledWith(
          expect.any(String),
          expect.objectContaining({
            body: expect.stringContaining('"planType":"monthly"'),
          })
        );
      });

      Object.defineProperty(window, 'location', {
        value: { href: originalHref },
        writable: true,
      });
    });
  });

  describe('status check error handling', () => {
    it('handles status check failure gracefully', async () => {
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
      (global.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('Network error'));

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Unlock/i)).toBeInTheDocument();
      });

      expect(consoleSpy).toHaveBeenCalledWith('Failed to check status:', expect.any(Error));
      consoleSpy.mockRestore();
    });

    it('handles missing token during status check', async () => {
      mockAuth.user = null;

      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Unlock/i)).toBeInTheDocument();
      });
    });
  });

  describe('UI elements', () => {
    beforeEach(() => {
      (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
        ok: true,
        json: () => Promise.resolve({ hasAccess: false }),
      });
    });

    it('shows discount badge for yearly billing', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('-17%')).toBeInTheDocument();
      });
    });

    it('shows "Most Popular" badge on Pro plan', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('Most Popular')).toBeInTheDocument();
      });
    });

    it('shows "Current Plan" button disabled for Starter', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText('Current Plan')).toBeInTheDocument();
      });

      const currentPlanButton = screen.getByText('Current Plan');
      expect(currentPlanButton).toBeDisabled();
    });

    it('shows security and guarantee info', async () => {
      render(<UpgradePage />);

      await waitFor(() => {
        expect(screen.getByText(/Secure payment via Stripe/i)).toBeInTheDocument();
      });

      expect(screen.getByText(/Cancel anytime/i)).toBeInTheDocument();
      expect(screen.getByText(/7-day money-back guarantee/i)).toBeInTheDocument();
    });
  });
});
