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

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

import { useChangelogBadge } from '@/hooks/useChangelogBadge';

import DashboardLayout from './DashboardLayout';

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    usePage: vi.fn(() => ({
      props: {
        auth: {
          user: {
            name: 'Test User',
            email: 'test@example.com',
          },
        },
        features: {
          notifications: false,
        },
      },
    })),
    Link: ({
      children,
      href,
      method: _method,
      as,
    }: {
      children: React.ReactNode;
      href: string;
      method?: string;
      as?: string;
    }) => {
      if (as === 'button') {
        return <button data-href={href}>{children}</button>;
      }
      return <a href={href}>{children}</a>;
    },
  };
});

// Mock the useTheme hook to avoid needing ThemeProvider
vi.mock('@/Components/theme/use-theme', () => ({
  useTheme: vi.fn(() => ({
    theme: 'system',
    setTheme: vi.fn(),
    resolvedTheme: 'light',
  })),
}));

// Mock useChangelogBadge so we can control badge state per test
vi.mock('@/hooks/useChangelogBadge', () => ({
  useChangelogBadge: vi.fn(() => ({ hasNew: false, markSeen: vi.fn() })),
}));

const mockedUsePage = vi.mocked(usePage);
const mockedUseChangelogBadge = vi.mocked(useChangelogBadge);

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

  beforeEach(() => {
    vi.clearAllMocks();
    localStorage.clear();
    mockedUsePage.mockReturnValue({
      props: {
        auth: {
          user: {
            name: 'Test User',
            email: 'test@example.com',
          },
        },
        features: {
          notifications: false,
        },
      },
    } as unknown as ReturnType<typeof usePage>);
    mockedUseChangelogBadge.mockReturnValue({ hasNew: false, markSeen: vi.fn() });
  });

  // ============================================
  // Rendering tests
  // ============================================

  describe('rendering', () => {
    it('renders children content', () => {
      render(
        <DashboardLayout>
          <div data-testid="child-content">Child Content</div>
        </DashboardLayout>,
      );

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

    it('renders within main element', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('main')).toBeInTheDocument();
    });

    it('has min-h-screen class', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('.min-h-screen')).toBeInTheDocument();
    });
  });

  // ============================================
  // Navigation tests
  // ============================================

  describe('navigation', () => {
    it('renders Dashboard link', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(screen.getByRole('link', { name: /dashboard/i })).toBeInTheDocument();
    });

    it('renders Profile link', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      // There may be multiple profile links (nav and dropdown)
      const profileLinks = screen.getAllByRole('link', { name: /profile/i });
      expect(profileLinks.length).toBeGreaterThan(0);
    });

    it('Dashboard link points to /dashboard', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const dashboardLink = screen.getByRole('link', { name: /dashboard/i });
      expect(dashboardLink).toHaveAttribute('href', '/dashboard');
    });
  });

  // ============================================
  // Logo tests
  // ============================================

  describe('logo', () => {
    it('renders logo link to dashboard', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const logoLink = screen
        .getAllByRole('link')
        .find((link) => link.getAttribute('href') === '/dashboard');
      expect(logoLink).toBeInTheDocument();
    });
  });

  // ============================================
  // User menu tests
  // ============================================

  describe('user menu', () => {
    it('renders user initial in avatar', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      // The avatar shows the first letter of the user's name
      expect(screen.getByText('T')).toBeInTheDocument();
    });

    it('shows user menu trigger button', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      // There should be a button that triggers the dropdown
      const avatarButtons = screen.getAllByRole('button');
      expect(avatarButtons.length).toBeGreaterThan(0);
    });

    it('opens dropdown menu when clicked', async () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      // Find and click the avatar button
      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) {
        await user.click(avatarButton);
      }

      // Dropdown should show user info
      expect(screen.getByText('Test User')).toBeInTheDocument();
      expect(screen.getByText('test@example.com')).toBeInTheDocument();
    });

    it('shows profile link in dropdown', async () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) {
        await user.click(avatarButton);
      }

      const profileLinks = screen.getAllByRole('link', { name: /profile/i });
      expect(profileLinks.length).toBeGreaterThan(0);
    });

    it('shows API Tokens link in dropdown when feature is enabled', async () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: {
            user: {
              name: 'Test User',
              email: 'test@example.com',
            },
          },
          features: {
            notifications: false,
            apiTokens: true,
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) {
        await user.click(avatarButton);
      }

      const tokenLinks = screen.getAllByText(/api tokens/i);
      expect(tokenLinks.length).toBeGreaterThan(0);
    });

    it('hides API Tokens link in dropdown when feature is disabled', async () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: {
            user: {
              name: 'Test User',
              email: 'test@example.com',
            },
          },
          features: {
            notifications: false,
            apiTokens: false,
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) {
        await user.click(avatarButton);
      }

      expect(screen.queryByText(/api tokens/i)).not.toBeInTheDocument();
    });

    it('shows logout option in dropdown', async () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) {
        await user.click(avatarButton);
      }

      expect(screen.getByText(/log out/i)).toBeInTheDocument();
    });
  });

  // ============================================
  // Mobile menu tests
  // ============================================

  describe('mobile menu', () => {
    it('renders mobile menu button', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(screen.getByRole('button', { name: /toggle navigation menu/i })).toBeInTheDocument();
    });

    it('mobile menu button is hidden on desktop (md:hidden)', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const mobileMenuButton = container.querySelector('.md\\:hidden');
      expect(mobileMenuButton).toBeInTheDocument();
    });

    it('opens mobile menu sheet when clicked', async () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const menuButton = screen.getByRole('button', { name: /toggle navigation menu/i });
      await user.click(menuButton);

      // Sheet should open with navigation items
      // Check for navigation links in the sheet
      const dashboardLinks = screen.getAllByRole('link', { name: /dashboard/i });
      expect(dashboardLinks.length).toBeGreaterThanOrEqual(1);
    });
  });

  // ============================================
  // Header tests
  // ============================================

  describe('header', () => {
    it('renders sticky header', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('.sticky')).toBeInTheDocument();
    });

    it('has proper height', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('.h-16')).toBeInTheDocument();
    });

    it('renders header element', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('header')).toBeInTheDocument();
    });
  });

  // ============================================
  // Different user tests
  // ============================================

  describe('different users', () => {
    it('shows first letter of different user name', () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: {
            user: {
              name: 'John Doe',
              email: 'john@example.com',
            },
          },
          features: { notifications: false },
        },
      } as unknown as ReturnType<typeof usePage>);

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

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

    it('shows different user info in dropdown', async () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: {
            user: {
              name: 'Jane Smith',
              email: 'jane@example.com',
            },
          },
          features: { notifications: false },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      const avatarButton = screen.getByText('J').closest('button');
      if (avatarButton) {
        await user.click(avatarButton);
      }

      expect(screen.getByText('Jane Smith')).toBeInTheDocument();
      expect(screen.getByText('jane@example.com')).toBeInTheDocument();
    });

    it('handles lowercase name', () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: {
            user: {
              name: 'lowercase user',
              email: 'lower@example.com',
            },
          },
          features: { notifications: false },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      // Should uppercase the first letter
      expect(screen.getByText('L')).toBeInTheDocument();
    });
  });

  // ============================================
  // Theme toggle tests
  // ============================================

  describe('theme toggle', () => {
    it('renders theme toggle', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      // Theme toggle should be present in the header
      const header = container.querySelector('header');
      expect(header).toBeInTheDocument();
    });
  });

  // ============================================
  // Footer tests
  // ============================================

  describe('footer', () => {
    it('renders footer with copyright text', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const year = new Date().getFullYear().toString();
      expect(screen.getByText(new RegExp(`${year} RankWiz`))).toBeInTheDocument();
    });

    it('renders support link', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const supportLink = screen.getByRole('link', { name: /support/i });
      expect(supportLink).toHaveAttribute('href', 'mailto:support@rankwiz.ai');
    });

    it('renders footer element', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('footer')).toBeInTheDocument();
    });
  });

  // ============================================
  // What's New badge tests (RET-004)
  // ============================================

  describe("What's New changelog badge", () => {
    it("shows What's New link in user dropdown", async () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) await user.click(avatarButton);

      expect(screen.getByRole('link', { name: /what.?s new/i })).toBeInTheDocument();
    });

    it("What's New link points to changelog", async () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) await user.click(avatarButton);

      const link = screen.getByRole('link', { name: /what.?s new/i });
      expect(link).toHaveAttribute('href', '/changelog');
    });

    it('shows badge dot when hasNew is true', async () => {
      mockedUseChangelogBadge.mockReturnValue({ hasNew: true, markSeen: vi.fn() });

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

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) await user.click(avatarButton);

      const badge = screen.getByLabelText('New changelog entries');
      expect(badge).toBeInTheDocument();
    });

    it('hides badge dot when hasNew is false', async () => {
      mockedUseChangelogBadge.mockReturnValue({ hasNew: false, markSeen: vi.fn() });

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

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) await user.click(avatarButton);

      expect(screen.queryByLabelText('New changelog entries')).not.toBeInTheDocument();
    });

    it('seeds localStorage LATEST_KEY when changelog.has_new is true', async () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: { user: { name: 'Test User', email: 'test@example.com' } },
          features: { notifications: false },
          changelog: {
            has_new: true,
            entries: [{ date: '2025-06-01', items: ['New feature added'] }],
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      await waitFor(() => {
        expect(localStorage.getItem('rankwiz_changelog_latest_at')).toBe(
          '2025-06-01T00:00:00.000Z',
        );
      });
    });

    it('does not seed localStorage when changelog.has_new is false', async () => {
      mockedUsePage.mockReturnValue({
        props: {
          auth: { user: { name: 'Test User', email: 'test@example.com' } },
          features: { notifications: false },
          changelog: {
            has_new: false,
            entries: [{ date: '2025-06-01', items: ['Old feature'] }],
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      // Wait a tick for any effects to run
      await waitFor(() => {
        expect(localStorage.getItem('rankwiz_changelog_latest_at')).toBeNull();
      });
    });

    it('does not overwrite a newer LATEST_KEY with an older date', async () => {
      // Simulate a newer entry already tracked in localStorage
      localStorage.setItem('rankwiz_changelog_latest_at', '2025-12-01T00:00:00.000Z');

      mockedUsePage.mockReturnValue({
        props: {
          auth: { user: { name: 'Test User', email: 'test@example.com' } },
          features: { notifications: false },
          changelog: {
            has_new: true,
            entries: [{ date: '2025-06-01', items: ['Older entry'] }],
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      await waitFor(() => {
        // Should NOT overwrite the newer value
        expect(localStorage.getItem('rankwiz_changelog_latest_at')).toBe(
          '2025-12-01T00:00:00.000Z',
        );
      });
    });

    it('dispatches changelog-latest-updated (not changelog-visited) after seeding localStorage', async () => {
      // Verifies the seeding effect uses a distinct event name so that
      // useChangelogBadge re-reads localStorage without treating this as a user visit.
      const dispatchSpy = vi.spyOn(window, 'dispatchEvent');

      mockedUsePage.mockReturnValue({
        props: {
          auth: { user: { name: 'Test User', email: 'test@example.com' } },
          features: { notifications: false },
          changelog: {
            has_new: true,
            entries: [{ date: '2025-06-01', items: ['New feature'] }],
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      await waitFor(() => {
        const eventNames = dispatchSpy.mock.calls.map((call) => (call[0] as Event).type);
        expect(eventNames).toContain('changelog-latest-updated');
        expect(eventNames).not.toContain('changelog-visited');
      });

      dispatchSpy.mockRestore();
    });

    it('does not call markSeen when clicking the What\'s New link — deferral is intentional', async () => {
      // markSeen is intentionally deferred to the /changelog page render (Changelog.tsx).
      // DashboardLayout only seeds LATEST_KEY; it does not mark entries as seen.
      // This test documents that the omission is deliberate, not accidental.
      const mockMarkSeen = vi.fn();
      mockedUseChangelogBadge.mockReturnValue({ hasNew: true, markSeen: mockMarkSeen });

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

      const avatarButton = screen.getByText('T').closest('button');
      if (avatarButton) await user.click(avatarButton);

      const link = screen.getByRole('link', { name: /what.?s new/i });
      await user.click(link);

      // markSeen must NOT be called here — it runs on the /changelog page, not on link click.
      expect(mockMarkSeen).not.toHaveBeenCalled();
    });

    it('uses the newest entry date regardless of server-returned order', async () => {
      // Entries returned oldest-first — the badge must still seed the newest date.
      mockedUsePage.mockReturnValue({
        props: {
          auth: { user: { name: 'Test User', email: 'test@example.com' } },
          features: { notifications: false },
          changelog: {
            has_new: true,
            entries: [
              { date: '2025-01-01', items: ['Old entry'] },
              { date: '2025-06-15', items: ['Newer entry'] },
              { date: '2025-03-10', items: ['Middle entry'] },
            ],
          },
        },
      } as unknown as ReturnType<typeof usePage>);

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

      await waitFor(() => {
        // Must seed the max date, not entries[0].date
        expect(localStorage.getItem('rankwiz_changelog_latest_at')).toBe(
          '2025-06-15T00:00:00.000Z',
        );
      });
    });
  });

  // ============================================
  // Accessibility tests
  // ============================================

  describe('accessibility', () => {
    it('has nav element for desktop navigation', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('nav')).toBeInTheDocument();
    });

    it('mobile menu button has accessible name', () => {
      render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const menuButton = screen.getByRole('button', { name: /toggle navigation menu/i });
      expect(menuButton).toBeInTheDocument();
    });

    it('has main element for content', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      expect(container.querySelector('main')).toBeInTheDocument();
    });

    it('has skip-to-content link as first focusable element', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const skipLink = container.querySelector('a[href="#main-content"]');
      expect(skipLink).toBeInTheDocument();
      expect(skipLink?.textContent).toBe('Skip to main content');
    });

    it('skip-to-content link is hidden by default', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const skipLink = container.querySelector('a[href="#main-content"]');
      expect(skipLink).toHaveClass('sr-only');
    });

    it('skip-to-content link becomes visible on focus', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const skipLink = container.querySelector('a[href="#main-content"]');
      expect(skipLink).toHaveClass('focus-visible:not-sr-only');
      expect(skipLink).toHaveClass('focus-visible:absolute');
    });

    it('main content has id for skip link to reference', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const mainContent = container.querySelector('main#main-content');
      expect(mainContent).toBeInTheDocument();
    });

    it('skip-to-content link points to main content', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const skipLink = container.querySelector('a[href="#main-content"]');
      expect(skipLink).toHaveAttribute('href', '#main-content');
    });

    it('skip-to-content link has proper focus styles', () => {
      const { container } = render(
        <DashboardLayout>
          <div>Content</div>
        </DashboardLayout>,
      );

      const skipLink = container.querySelector('a[href="#main-content"]');
      expect(skipLink).toHaveClass('focus-visible:ring-2');
      expect(skipLink).toHaveClass('focus-visible:ring-ring');
      expect(skipLink).toHaveClass('focus-visible:outline-none');
    });
  });
});
