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

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

import Webhooks from './Webhooks';

vi.mock('@inertiajs/react', async () => {
  const actual = await vi.importActual('@inertiajs/react');
  return {
    ...actual,
    usePage: vi.fn(() => ({
      props: {
        auth: { user: { id: 1, name: 'Test', email: 'test@example.com', has_password: true } },
        flash: {},
        errors: {},
        features: {
          twoFactor: false,
          billing: false,
          socialAuth: false,
          emailVerification: true,
          apiTokens: true,
          userSettings: true,
          notifications: false,
          onboarding: false,
          apiDocs: false,
          webhooks: true,
        },
        notifications_unread_count: 0,
      },
    })),
    Head: ({ title }: { title: string }) => <title>{title}</title>,
    Link: ({ children, href }: { children: React.ReactNode; href: string }) => (
      <a href={href}>{children}</a>
    ),
  };
});

vi.mock('@/Layouts/DashboardLayout', () => ({
  default: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="dashboard-layout">{children}</div>
  ),
}));

vi.mock('@/Components/layout/EditorialPageHeader', () => ({
  default: ({
    title,
    subtitle,
    actions,
  }: {
    title: string;
    subtitle?: string;
    actions?: React.ReactNode;
  }) => (
    <div data-testid="page-header">
      <h1>{title}</h1>
      {subtitle && <p>{subtitle}</p>}
      {actions}
    </div>
  ),
}));

vi.mock('@/Components/ui/confirm-dialog', () => ({
  ConfirmDialog: () => null,
}));

// Mock fetch for endpoint loading
global.fetch = vi.fn().mockResolvedValue({
  ok: true,
  json: async () => [],
}) as typeof fetch;

describe('Webhooks Page', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => [],
    }) as typeof fetch;
  });

  it('renders the webhooks page with DashboardLayout', () => {
    render(<Webhooks available_events={['user.created', 'user.updated']} />);

    expect(screen.getByTestId('dashboard-layout')).toBeInTheDocument();
  });

  it('renders page header', () => {
    render(<Webhooks available_events={['user.created']} />);

    expect(screen.getByTestId('page-header')).toBeInTheDocument();
    expect(screen.getByRole('heading', { name: /webhooks/i })).toBeInTheDocument();
  });

  it('renders add endpoint button', () => {
    render(<Webhooks available_events={['user.created']} />);

    expect(screen.getByRole('button', { name: /add endpoint/i })).toBeInTheDocument();
  });

  it('shows empty state when no endpoints', async () => {
    render(<Webhooks available_events={['user.created']} />);

    // Wait for the fetch to resolve and component to update
    const emptyMessage = await screen.findByText(/No endpoints configured/i);
    expect(emptyMessage).toBeInTheDocument();
  });

  it('shows ErrorWithRetry when endpoint fetch fails', async () => {
    global.fetch = vi.fn().mockResolvedValue({
      ok: false,
      status: 500,
      json: async () => ({}),
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created']} />);

    expect(await screen.findByText(/couldn't load webhooks/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Try Again/i })).toBeInTheDocument();
  });

  it('shows loading state initially', () => {
    // Don't resolve the fetch immediately
    (global.fetch as ReturnType<typeof vi.fn>).mockReturnValue(new Promise(() => {}));

    const { container } = render(<Webhooks available_events={['user.created']} />);

    // Loading state renders skeleton placeholders, not a text label
    expect(container.querySelector('.animate-pulse')).toBeInTheDocument();
  });

  it('renders endpoint list when endpoints exist', async () => {
    const mockEndpoints = [
      {
        id: 1,
        url: 'https://example.com/webhook',
        events: ['user.created'],
        active: true,
        secret: 'whsec_test123',
      },
    ];

    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => mockEndpoints,
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created']} />);

    expect(await screen.findByText('https://example.com/webhook')).toBeInTheDocument();
  });

  it('displays active status badge for active endpoints', async () => {
    const mockEndpoints = [
      {
        id: 1,
        url: 'https://example.com/webhook',
        events: ['user.created'],
        active: true,
        secret: 'whsec_test123',
      },
    ];

    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => mockEndpoints,
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created']} />);

    expect(await screen.findByText('Active')).toBeInTheDocument();
  });

  it('displays event subscriptions for each endpoint', async () => {
    const mockEndpoints = [
      {
        id: 1,
        url: 'https://example.com/webhook',
        events: ['user.created', 'user.updated'],
        active: true,
        secret: 'whsec_test123',
      },
    ];

    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => mockEndpoints,
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created', 'user.updated']} />);

    await screen.findByText('https://example.com/webhook');

    expect(screen.getByText('user.created')).toBeInTheDocument();
    expect(screen.getByText('user.updated')).toBeInTheDocument();
  });

  // ============================================
  // R21-WH-P1-2: dead "Show secret" control removed — index API never returns secret
  // ============================================

  it('R21-WH-P1-2: no "Show secret" affordance in endpoint cards (index payload has no secret)', async () => {
    // The real GET /api/webhooks index never returns `secret` — it is write-once and $hidden on the model.
    const mockEndpoints = [
      {
        id: 1,
        url: 'https://example.com/webhook',
        events: ['user.created'],
        active: true,
        // secret intentionally absent — mirrors real index payload
      },
    ];

    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => mockEndpoints,
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created']} />);

    await screen.findByText('https://example.com/webhook');

    // The toggle must not exist at all — it was a permanently-dead no-op
    expect(screen.queryByRole('button', { name: /show secret/i })).not.toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /hide secret/i })).not.toBeInTheDocument();
  });

  // ============================================
  // R3UXA-005: Deliveries panel shows for ANY selected endpoint, including empty state
  // ============================================

  it('R3UXA-005: shows deliveries panel with empty state when endpoint has no deliveries', async () => {
    const user = userEvent.setup();
    const mockEndpoints = [
      { id: 1, url: 'https://example.com/webhook', events: ['user.created'], active: true, secret: 'whsec_abc' },
    ];
    let fetchCallCount = 0;
    global.fetch = vi.fn().mockImplementation((url: string) => {
      fetchCallCount++;
      if (typeof url === 'string' && url.includes('/deliveries')) {
        return Promise.resolve({ ok: true, json: async () => [] });
      }
      return Promise.resolve({ ok: true, json: async () => fetchCallCount === 1 ? mockEndpoints : [] });
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created']} />);
    await screen.findByText('https://example.com/webhook');

    await user.click(screen.getByRole('button', { name: /deliveries/i }));

    // Panel title and empty-state message should appear
    await waitFor(() => {
      expect(screen.getByText(/recent deliveries/i)).toBeInTheDocument();
      expect(screen.getByText(/no deliveries yet/i)).toBeInTheDocument();
    });
  });

  // ============================================
  // R3FE-011: res.ok check — non-2xx shows error toast
  // ============================================

  it('R3FE-011: shows error toast when deliveries fetch returns non-ok response', async () => {
    const user = userEvent.setup();
    const mockEndpoints = [
      { id: 1, url: 'https://example.com/webhook', events: ['user.created'], active: true, secret: 'whsec_abc' },
    ];
    global.fetch = vi.fn().mockImplementation((url: string) => {
      if (typeof url === 'string' && url.includes('/deliveries')) {
        return Promise.resolve({ ok: false, status: 500 });
      }
      return Promise.resolve({ ok: true, json: async () => mockEndpoints });
    }) as typeof fetch;

    render(<Webhooks available_events={['user.created']} />);
    await screen.findByText('https://example.com/webhook');

    vi.mocked(toast.error).mockClear();

    await user.click(screen.getByRole('button', { name: /deliveries/i }));

    await waitFor(() => {
      expect(vi.mocked(toast.error)).toHaveBeenCalledTimes(1);
    });
  });
});
