/**
 * Component: Header Component Tests
 * Documentation: documentation/frontend/components.md
 */

// @vitest-environment jsdom

import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '../../helpers/render';

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

  beforeAll(async () => {
    ({ Header } = await import('@/components/layout/Header'));
  });

  beforeEach(() => {
    localStorage.clear();
  });

  afterEach(() => {
    vi.unstubAllGlobals();
  });

  it('renders login button and opens Plex auth window', async () => {
    const fetchMock = vi.fn().mockImplementation((input: RequestInfo) => {
      if (input === '/api/version') {
        return Promise.resolve({
          json: vi.fn().mockResolvedValue({ version: 'v.test' }),
        });
      }

      return Promise.resolve({
        json: vi.fn().mockResolvedValue({ success: true, authUrl: 'https://plex.example/login' }),
      });
    });
    const openMock = vi.spyOn(window, 'open').mockImplementation(() => null);

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, { auth: { user: null, isLoading: false } });

    const loginButton = screen.getByRole('button', { name: /login with plex/i });
    await userEvent.click(loginButton);

    await waitFor(() => {
      expect(fetchMock).toHaveBeenCalledWith('/api/auth/plex/login', { method: 'POST' });
    });
    expect(openMock).toHaveBeenCalledWith(
      'https://plex.example/login',
      'plex-auth',
      'width=600,height=700'
    );
  });

  it('renders admin navigation and user menu actions for local users', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: vi.fn().mockResolvedValue({ version: 'v.test' }),
    });

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'admin-1',
          plexId: 'plex-1',
          username: 'admin',
          role: 'admin',
          authProvider: 'local',
        },
        isLoading: false,
      },
    });

    expect(screen.getByRole('link', { name: 'Admin' })).toBeInTheDocument();
    expect(screen.getByRole('link', { name: 'My Requests' })).toBeInTheDocument();

    const userButton = screen.getByText('admin').closest('button');
    expect(userButton).not.toBeNull();
    await userEvent.click(userButton as HTMLButtonElement);

    await waitFor(() => {
      expect(screen.getByText('Change Password')).toBeInTheDocument();
    });
    expect(screen.getByText('Logout')).toBeInTheDocument();
  });

  it('shows BookDate link and avatar when BookDate is enabled', async () => {
    localStorage.setItem('accessToken', 'token');
    const fetchMock = vi.fn().mockImplementation((input: RequestInfo) => {
      if (input === '/api/version') {
        return Promise.resolve({
          json: vi.fn().mockResolvedValue({ version: 'v.test' }),
        });
      }
      if (input === '/api/bookdate/config') {
        return Promise.resolve({
          json: vi.fn().mockResolvedValue({
            config: { isVerified: true, isEnabled: true },
          }),
        });
      }
      return Promise.resolve({
        json: vi.fn().mockResolvedValue({}),
      });
    });

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'user-1',
          plexId: 'plex-1',
          username: 'reader',
          role: 'user',
          avatarUrl: '/avatar.png',
        },
        isLoading: false,
      },
    });

    await waitFor(() => {
      expect(screen.getByRole('link', { name: 'BookDate' })).toBeInTheDocument();
    });
    expect(screen.getByAltText('reader')).toBeInTheDocument();
  });

  it('logs out from the user menu and shows initials fallback', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: vi.fn().mockResolvedValue({ version: 'v.test' }),
    });
    const logoutMock = vi.fn();

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'user-2',
          plexId: 'plex-2',
          username: 'alice',
          role: 'user',
          authProvider: 'plex',
        },
        logout: logoutMock,
        isLoading: false,
      },
    });

    expect(screen.getByText(/^A$/)).toBeInTheDocument();

    const userButton = screen.getByText('alice').closest('button');
    expect(userButton).not.toBeNull();
    await userEvent.click(userButton as HTMLButtonElement);

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

    await userEvent.click(screen.getByText('Logout'));

    expect(logoutMock).toHaveBeenCalledTimes(1);
  });

  it('toggles the mobile menu and closes after navigation', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: vi.fn().mockResolvedValue({ version: 'v.test' }),
    });
    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, { auth: { user: null, isLoading: false } });

    const initialHomeLinks = screen.getAllByRole('link', { name: 'Home' }).length;

    await userEvent.click(screen.getByRole('button', { name: 'Toggle menu' }));

    const openHomeLinks = screen.getAllByRole('link', { name: 'Home' });
    expect(openHomeLinks).toHaveLength(initialHomeLinks + 1);

    await userEvent.click(openHomeLinks[openHomeLinks.length - 1]);
    expect(screen.getAllByRole('link', { name: 'Home' })).toHaveLength(initialHomeLinks);
  });

  it('hides BookDate when config check fails', async () => {
    localStorage.setItem('accessToken', 'token');
    const errorMock = vi.spyOn(console, 'error').mockImplementation(() => undefined);
    const fetchMock = vi.fn().mockImplementation((input: RequestInfo) => {
      if (input === '/api/version') {
        return Promise.resolve({
          json: vi.fn().mockResolvedValue({ version: 'v.test' }),
        });
      }
      if (input === '/api/bookdate/config') {
        return Promise.reject(new Error('boom'));
      }
      return Promise.resolve({
        json: vi.fn().mockResolvedValue({}),
      });
    });

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'user-3',
          plexId: 'plex-3',
          username: 'reader',
          role: 'user',
        },
        isLoading: false,
      },
    });

    await waitFor(() => {
      expect(errorMock).toHaveBeenCalledWith('Failed to check BookDate config:', expect.any(Error));
    });

    expect(screen.queryByRole('link', { name: 'BookDate' })).not.toBeInTheDocument();
  });

  it('opens change password modal and closes it', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: vi.fn().mockResolvedValue({ version: 'v.test' }),
    });
    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'user-4',
          plexId: 'plex-4',
          username: 'local-admin',
          role: 'admin',
          authProvider: 'local',
        },
        isLoading: false,
      },
    });

    const userButton = screen.getByText('local-admin').closest('button');
    expect(userButton).not.toBeNull();
    await userEvent.click(userButton as HTMLButtonElement);

    const changePasswordButton = await screen.findByText('Change Password');
    await userEvent.click(changePasswordButton);

    expect(await screen.findByRole('heading', { name: 'Change Password' })).toBeInTheDocument();

    await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
    await waitFor(() => {
      expect(screen.queryByRole('heading', { name: 'Change Password' })).not.toBeInTheDocument();
    });
  });

  it('closes the user menu when profile is clicked', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: vi.fn().mockResolvedValue({ version: 'v.test' }),
    });
    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'user-5',
          plexId: 'plex-5',
          username: 'reader',
          role: 'user',
          authProvider: 'local',
        },
        isLoading: false,
      },
    });

    const userButton = screen.getByText('reader').closest('button');
    expect(userButton).not.toBeNull();
    await userEvent.click(userButton as HTMLButtonElement);

    const profileLink = await screen.findByRole('link', { name: 'Profile' });
    await userEvent.click(profileLink);

    await waitFor(() => {
      expect(screen.queryByText('Logout')).not.toBeInTheDocument();
    });
  });

  it('logs errors when Plex login fails', async () => {
    const fetchMock = vi.fn().mockRejectedValue(new Error('login failed'));
    const errorMock = vi.spyOn(console, 'error').mockImplementation(() => undefined);
    const openMock = vi.spyOn(window, 'open').mockImplementation(() => null);

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, { auth: { user: null, isLoading: false } });

    await userEvent.click(screen.getByRole('button', { name: /login with plex/i }));

    await waitFor(() => {
      expect(errorMock).toHaveBeenCalledWith('Login failed:', expect.any(Error));
    });
    expect(openMock).not.toHaveBeenCalled();
  });

  it('closes the mobile menu when BookDate is selected', async () => {
    localStorage.setItem('accessToken', 'token');
    const fetchMock = vi.fn().mockImplementation((input: RequestInfo) => {
      if (input === '/api/version') {
        return Promise.resolve({
          json: vi.fn().mockResolvedValue({ version: 'v.test' }),
        });
      }
      if (input === '/api/bookdate/config') {
        return Promise.resolve({
          json: vi.fn().mockResolvedValue({
            config: { isVerified: true, isEnabled: true },
          }),
        });
      }
      return Promise.resolve({
        json: vi.fn().mockResolvedValue({}),
      });
    });

    vi.stubGlobal('fetch', fetchMock);

    renderWithProviders(<Header />, {
      auth: {
        user: {
          id: 'user-6',
          plexId: 'plex-6',
          username: 'reader',
          role: 'user',
        },
        isLoading: false,
      },
    });

    const initialBookDateCount = (await screen.findAllByRole('link', { name: 'BookDate' })).length;

    await userEvent.click(screen.getByRole('button', { name: 'Toggle menu' }));

    const bookDateLinks = await screen.findAllByRole('link', { name: 'BookDate' });
    expect(bookDateLinks).toHaveLength(initialBookDateCount + 1);

    await userEvent.click(bookDateLinks[bookDateLinks.length - 1]);

    await waitFor(async () => {
      const remainingLinks = await screen.findAllByRole('link', { name: 'BookDate' });
      expect(remainingLinks).toHaveLength(initialBookDateCount);
    });
  });
});
