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

import { router, usePage } from '@inertiajs/react';

import AdminLayout from './AdminLayout';

type MockSidebarProps = {
  children: React.ReactNode;
  navigationGroups?: { label: string; items: { label: string; href: string }[] }[];
  navTopContent?: React.ReactNode;
  groupCollapsedState?: Record<string, boolean>;
  onGroupToggle?: (label: string) => void;
};

// Expose new props so collapse/search behavior can be tested
vi.mock('@/Components/sidebar/sidebar-layout', () => ({
  default: ({
    children,
    navigationGroups,
    navTopContent,
    groupCollapsedState,
    onGroupToggle,
  }: MockSidebarProps) => (
    <div data-testid="sidebar-layout">
      {navTopContent && <div data-testid="nav-top-content">{navTopContent}</div>}
      {navigationGroups?.map((group) => (
        <div key={group.label}>
          <button
            type="button"
            data-testid={`group-toggle-${group.label}`}
            onClick={() => onGroupToggle?.(group.label)}
          >
            {group.label}
          </button>
          {!groupCollapsedState?.[group.label] &&
            group.items.map((item) => (
              <a key={item.href} href={item.href}>
                {item.label}
              </a>
            ))}
        </div>
      ))}
      {children}
    </div>
  ),
}));

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    usePage: vi.fn(() => ({
      props: {
        auth: {
          user: { admin_role: 'operator' },
        },
        features: {
          billing: true,
          webhooks: true,
          apiTokens: true,
          socialAuth: true,
          notifications: true,
          twoFactor: true,
        },
        admin_failed_job_count: 0,
      },
    })),
    router: {
      ...(actual.router as Record<string, unknown>),
      on: vi.fn(() => vi.fn()),
    },
    Link: ({ children, href }: { children: React.ReactNode; href: string }) => (
      <a href={href}>{children}</a>
    ),
  };
});

vi.mock('@/Components/admin/AdminCommandPalette', () => ({
  AdminCommandPalette: () => <div data-testid="admin-command-palette" />,
}));

vi.mock('@/Components/admin/AdminPageErrorBoundary', () => ({
  AdminPageErrorBoundary: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="admin-error-boundary">{children}</div>
  ),
}));

vi.mock('nprogress', () => ({
  default: {
    configure: vi.fn(),
    start: vi.fn(),
    done: vi.fn(),
  },
}));

const mockedUsePage = vi.mocked(usePage);
const mockedRouterOn = vi.mocked(router.on);

const defaultPageProps = {
  props: {
    auth: {
      user: { admin_role: 'operator' },
    },
    features: {
      billing: true,
      webhooks: true,
      apiTokens: true,
      socialAuth: true,
      notifications: true,
      twoFactor: true,
    },
    admin_failed_job_count: 0,
  },
};

describe('AdminLayout', () => {
  const user = userEvent.setup();

  beforeEach(() => {
    vi.clearAllMocks();
    localStorage.clear();
    mockedUsePage.mockReturnValue(defaultPageProps as unknown as ReturnType<typeof usePage>);
    mockedRouterOn.mockReturnValue(vi.fn());
  });

  it('renders children content', () => {
    render(
      <AdminLayout>
        <div data-testid="child-content">Hello Admin</div>
      </AdminLayout>,
    );

    expect(screen.getByTestId('child-content')).toBeInTheDocument();
    expect(screen.getByText('Hello Admin')).toBeInTheDocument();
  });

  it('wraps children in AdminPageErrorBoundary', () => {
    render(
      <AdminLayout>
        <div>Content</div>
      </AdminLayout>,
    );

    const boundary = screen.getByTestId('admin-error-boundary');
    expect(boundary).toBeInTheDocument();
    expect(boundary).toHaveTextContent('Content');
  });

  it('renders AdminCommandPalette', () => {
    render(
      <AdminLayout>
        <div>Content</div>
      </AdminLayout>,
    );

    expect(screen.getByTestId('admin-command-palette')).toBeInTheDocument();
  });

  describe('NProgress loading indicator', () => {
    it('configures NProgress on mount', async () => {
      const NProgress = await import('nprogress');

      render(
        <AdminLayout>
          <div>Content</div>
        </AdminLayout>,
      );

      expect(NProgress.default.configure).toHaveBeenCalledWith({ showSpinner: false });
    });

    it('registers Inertia start and finish event listeners', () => {
      render(
        <AdminLayout>
          <div>Content</div>
        </AdminLayout>,
      );

      expect(mockedRouterOn).toHaveBeenCalledWith('start', expect.any(Function));
      expect(mockedRouterOn).toHaveBeenCalledWith('finish', expect.any(Function));
    });

    it('starts NProgress on Inertia start event', async () => {
      const NProgress = await import('nprogress');
      let startCallback: (() => void) | undefined;

      mockedRouterOn.mockImplementation((event, callback) => {
        if (event === 'start') {
          startCallback = callback as () => void;
        }
        return vi.fn();
      });

      render(
        <AdminLayout>
          <div>Content</div>
        </AdminLayout>,
      );

      expect(startCallback).toBeDefined();
      startCallback!();
      expect(NProgress.default.start).toHaveBeenCalled();
    });

    it('finishes NProgress on Inertia finish event', async () => {
      const NProgress = await import('nprogress');
      let finishCallback: (() => void) | undefined;

      mockedRouterOn.mockImplementation((event, callback) => {
        if (event === 'finish') {
          finishCallback = callback as () => void;
        }
        return vi.fn();
      });

      render(
        <AdminLayout>
          <div>Content</div>
        </AdminLayout>,
      );

      expect(finishCallback).toBeDefined();
      finishCallback!();
      expect(NProgress.default.done).toHaveBeenCalled();
    });

    it('cleans up event listeners on unmount', async () => {
      const NProgress = await import('nprogress');
      const removeStart = vi.fn();
      const removeFinish = vi.fn();

      mockedRouterOn.mockImplementation((event) => {
        if (event === 'start') return removeStart;
        if (event === 'finish') return removeFinish;
        return vi.fn();
      });

      const { unmount } = render(
        <AdminLayout>
          <div>Content</div>
        </AdminLayout>,
      );

      unmount();

      expect(removeStart).toHaveBeenCalled();
      expect(removeFinish).toHaveBeenCalled();
      expect(NProgress.default.done).toHaveBeenCalled();
    });
  });

  describe('nav search filter', () => {
    it('renders nav filter input in navTopContent', () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );
      expect(screen.getByRole('searchbox', { name: /filter navigation/i })).toBeInTheDocument();
    });

    it('shows all nav groups when search is empty', () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );
      expect(screen.getByTestId('group-toggle-Overview')).toBeInTheDocument();
      expect(screen.getByTestId('group-toggle-Management')).toBeInTheDocument();
      expect(screen.getByTestId('group-toggle-System')).toBeInTheDocument();
    });

    it('filters nav items by search query', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      const input = screen.getByRole('searchbox', { name: /filter navigation/i });
      await user.type(input, 'users');

      expect(screen.getByText('Users')).toBeInTheDocument();
      expect(screen.queryByText('Blog Posts')).not.toBeInTheDocument();
    });

    it('hides groups with no matching items', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      const input = screen.getByRole('searchbox', { name: /filter navigation/i });
      await user.type(input, 'xyznotfound');

      expect(screen.queryByTestId('group-toggle-Overview')).not.toBeInTheDocument();
    });

    it('restores all groups after clearing search', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      const input = screen.getByRole('searchbox', { name: /filter navigation/i });
      await user.type(input, 'users');
      await user.clear(input);

      expect(screen.getByTestId('group-toggle-Overview')).toBeInTheDocument();
      expect(screen.getByTestId('group-toggle-Content')).toBeInTheDocument();
    });
  });

  describe('collapsible groups', () => {
    it('all groups start expanded by default', () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );
      // Items of Overview group visible (Metrics link)
      expect(screen.getByText('Metrics')).toBeInTheDocument();
    });

    it('collapses a group when its toggle is clicked', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      expect(screen.getByText('Metrics')).toBeInTheDocument();

      await user.click(screen.getByTestId('group-toggle-Overview'));

      expect(screen.queryByText('Metrics')).not.toBeInTheDocument();
    });

    it('expands a collapsed group on second click', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      await user.click(screen.getByTestId('group-toggle-Overview'));
      await user.click(screen.getByTestId('group-toggle-Overview'));

      expect(screen.getByText('Metrics')).toBeInTheDocument();
    });

    it('persists collapsed groups to localStorage', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      await user.click(screen.getByTestId('group-toggle-Overview'));

      const stored = JSON.parse(
        localStorage.getItem('admin_nav_collapsed_groups') ?? '{}',
      ) as Record<string, boolean>;
      expect(stored['Overview']).toBe(true);
    });

    it('initializes collapsed state from localStorage', () => {
      localStorage.setItem('admin_nav_collapsed_groups', JSON.stringify({ Overview: true }));

      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      // Overview collapsed → Metrics hidden
      expect(screen.queryByText('Metrics')).not.toBeInTheDocument();
      // Management expanded → Users visible
      expect(screen.getByText('Users')).toBeInTheDocument();
    });

    it('collapsing one group does not affect others', async () => {
      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      await user.click(screen.getByTestId('group-toggle-Overview'));

      expect(screen.getByText('Users')).toBeInTheDocument();
    });
  });

  describe('failed job badge', () => {
    it('adds badge count to System Info when failed jobs exist', () => {
      mockedUsePage.mockReturnValue({
        props: { ...defaultPageProps.props, admin_failed_job_count: 5 },
      } as unknown as ReturnType<typeof usePage>);

      render(
        <AdminLayout>
          <div>content</div>
        </AdminLayout>,
      );

      expect(screen.getByText('System Info')).toBeInTheDocument();
    });
  });
});
