import { render, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { useRouter } from 'next/navigation';
import CallbackPage from '../page';

vi.mock('next/navigation', () => ({
  useRouter: vi.fn(),
}));

const setAuthMock = vi.fn();
vi.mock('@/stores/auth', () => ({
  useAuthStore: () => ({ setAuth: setAuthMock }),
}));

const meMock = vi.fn();
vi.mock('@/lib/api', () => ({
  api: { auth: { me: (...a: unknown[]) => meMock(...a) } },
}));

const originalLocation = window.location;

function setHash(hash: string) {
  Object.defineProperty(window, 'location', {
    value: { ...originalLocation, hash },
    writable: true,
    configurable: true,
  });
}

describe('CallbackPage', () => {
  const replace = vi.fn();

  beforeEach(() => {
    sessionStorage.clear();
    replace.mockClear();
    setAuthMock.mockReset();
    meMock.mockReset();
    localStorage.clear();
    vi.mocked(useRouter).mockReturnValue({
      replace,
      push: vi.fn(),
      back: vi.fn(),
      forward: vi.fn(),
      refresh: vi.fn(),
      prefetch: vi.fn(),
    } as unknown as ReturnType<typeof useRouter>);
  });

  afterEach(() => {
    Object.defineProperty(window, 'location', {
      value: originalLocation,
      writable: true,
      configurable: true,
    });
  });

  it('resumes a pending invitation after OAuth without placing its token in a URL', async () => {
    sessionStorage.setItem('alqove.pending-invitation', 'invite-secret');
    setHash('#token=abc123');
    meMock.mockResolvedValue({ data: { id: 'u1', email: 'invited@example.com' } });
    render(<CallbackPage />);
    await waitFor(() => expect(replace).toHaveBeenCalledWith('/invitations/accept'));
    expect(sessionStorage.getItem('alqove.pending-invitation')).toBe('invite-secret');
  });

  it('redirects to /login when no token is in the hash', async () => {
    setHash('');
    render(<CallbackPage />);
    await waitFor(() => expect(replace).toHaveBeenCalledWith('/login'));
    expect(meMock).not.toHaveBeenCalled();
  });

  it('with a token: stores it, fetches /me, sets auth, and routes home', async () => {
    setHash('#token=abc123');
    meMock.mockResolvedValue({ data: { id: 'u1', email: 'jane@example.com' } });
    render(<CallbackPage />);
    await waitFor(() => expect(setAuthMock).toHaveBeenCalled());
    expect(localStorage.getItem('auth_token')).toBe('abc123');
    expect(setAuthMock).toHaveBeenCalledWith(
      { id: 'u1', email: 'jane@example.com' },
      'abc123',
    );
    expect(replace).toHaveBeenCalledWith('/');
  });

  it('clears token and redirects to /login if /me fails', async () => {
    setHash('#token=stale');
    meMock.mockRejectedValue(new Error('401'));
    render(<CallbackPage />);
    await waitFor(() => expect(replace).toHaveBeenCalledWith('/login'));
    expect(localStorage.getItem('auth_token')).toBeNull();
    expect(setAuthMock).not.toHaveBeenCalled();
  });
});
