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

const { routerPostMock } = vi.hoisted(() => ({ routerPostMock: vi.fn() }));

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    Head: ({ title }: { title: string }) => <title>{title}</title>,
    usePage: vi.fn(() => ({
      url: '/admin/dlq/1',
      props: {
        auth: { user: { id: 1, name: 'Admin', email: 'admin@test.com', is_admin: true } },
        features: {
          billing: false,
          socialAuth: false,
          emailVerification: true,
          apiTokens: true,
          userSettings: true,
          notifications: false,
          onboarding: false,
          apiDocs: false,
          twoFactor: false,
          webhooks: false,
          admin: true,
        },
      },
    })),
    Link: ({
      children,
      href,
      ...rest
    }: {
      children: React.ReactNode;
      href: string;
      className?: string;
    }) => (
      <a href={href} {...rest}>
        {children}
      </a>
    ),
    router: {
      post: routerPostMock,
      patch: vi.fn(),
      delete: vi.fn(),
      get: vi.fn(),
      visit: vi.fn(),
      on: vi.fn(() => vi.fn()),
    },
  };
});

vi.mock('@/Components/theme/use-theme', () => ({
  useTheme: vi.fn(() => ({ theme: 'system', setTheme: vi.fn(), resolvedTheme: 'light' })),
}));

vi.mock('sonner', () => ({
  toast: { success: vi.fn(), error: vi.fn() },
}));

import DlqShow from './Show';

const baseJob = {
  id: 1,
  job_class: 'App\\Jobs\\ExampleJob',
  queue: 'default',
  connection: 'database',
  retryable: true,
  replayed_at: null,
  exception: null,
  payload: { command: 'serialized-data', displayName: 'App\\Jobs\\ExampleJob' },
  created_at: '2026-01-01T00:00:00.000Z',
};

const mockPreview = {
  job_class: 'App\\Jobs\\ExampleJob',
  queue: 'default',
  connection: 'database',
  attempts: 3,
  last_exception: 'Sample exception text',
  retryable: true,
  replayed_at: null,
  payload_size_bytes: 256,
  payload_preview: '{"command":"serialized-data"}',
};

const mockFetchOk = (body: unknown) =>
  vi.fn(() =>
    Promise.resolve(
      new Response(JSON.stringify(body), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      }),
    ),
  );

describe('DlqShow', () => {
  beforeEach(() => {
    routerPostMock.mockReset();
    vi.stubGlobal('fetch', mockFetchOk(mockPreview));
  });

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

  it('renders the payload in an editable textarea', () => {
    render(<DlqShow job={baseJob} />);
    const textarea = screen.getByLabelText(/payload/i);
    expect(textarea).toBeInTheDocument();
    expect(textarea.tagName).toBe('TEXTAREA');
  });

  it('blocks the preview fetch when payload is not valid JSON and shows inline error', async () => {
    const user = userEvent.setup();
    render(<DlqShow job={baseJob} />);
    const textarea = screen.getByLabelText(/payload/i);
    await user.clear(textarea);
    await user.type(textarea, '{{ invalid json');
    await user.click(screen.getByRole('button', { name: /retry/i }));
    expect(screen.getByText(/payload is not valid json/i)).toBeInTheDocument();
    expect(global.fetch).not.toHaveBeenCalled();
    expect(routerPostMock).not.toHaveBeenCalled();
  });

  it('opens preview dialog after fetching, then submits replay on confirm', async () => {
    const user = userEvent.setup();
    render(<DlqShow job={baseJob} />);
    await user.click(screen.getByRole('button', { name: /retry/i }));

    await waitFor(() => {
      expect(screen.getByRole('alertdialog')).toBeInTheDocument();
    });
    expect(screen.getByText(/replay this dead-letter job/i)).toBeInTheDocument();
    expect(global.fetch).toHaveBeenCalledOnce();
    expect(routerPostMock).not.toHaveBeenCalled();

    await user.click(screen.getByRole('button', { name: /^replay job$/i }));
    expect(routerPostMock).toHaveBeenCalledOnce();
  });

  it('shows an error if the preview fetch fails', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn(() => Promise.resolve(new Response('', { status: 500 }))),
    );
    const user = userEvent.setup();
    render(<DlqShow job={baseJob} />);
    await user.click(screen.getByRole('button', { name: /retry/i }));
    await waitFor(() => {
      expect(screen.getByText(/could not load job preview/i)).toBeInTheDocument();
    });
    expect(routerPostMock).not.toHaveBeenCalled();
  });

  it('does not show the retry button when job has already been replayed', () => {
    render(<DlqShow job={{ ...baseJob, replayed_at: '2026-01-02T00:00:00.000Z' }} />);
    expect(screen.queryByRole('button', { name: /retry/i })).not.toBeInTheDocument();
  });

  it('does not show the retry button when job is not retryable', () => {
    render(<DlqShow job={{ ...baseJob, retryable: false }} />);
    expect(screen.queryByRole('button', { name: /retry/i })).not.toBeInTheDocument();
  });
});
