import { App as AntApp, ConfigProvider } from 'antd';
import { act, render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createMemoryRouter, RouterProvider, useLocation } from 'react-router-dom';
import { describe, expect, it, vi } from 'vitest';

import { I18nProvider } from '../../src/frontend/i18n/react';
import { api } from '../../src/frontend/services/api';
import { mailhubTheme } from '../../src/frontend/theme';
import type { Webhook, WebhookDelivery, WebhookDeliveryFilters } from '../../src/frontend/types';
import Webhooks from '../../src/pages/Webhooks';

describe('Webhook delivery detail', () => {
  it('opens from the endpoint list and loads deliveries for only that webhook', async () => {
    const user = userEvent.setup();
    const webhookDeliveries = mockWebhookApis({ detailDeliveries: [failedDelivery] });
    const router = renderPage('/integrations/webhooks');

    await screen.findByText(webhook.name);
    await user.click(screen.getByRole('button', { name: '查看投递' }));

    await waitFor(() => expect(router.state.location.search).toBe('?webhookId=7'));
    await waitFor(() => expect(webhookDeliveries).toHaveBeenCalledWith({
      webhookId: webhook.id,
      status: undefined,
      eventType: undefined,
      limit: 200
    }));
    expect(await screen.findByText('Connection refused by receiver')).not.toBeNull();
  });

  it('stores delivery filters in the URL and sends them to the server', async () => {
    const user = userEvent.setup();
    const webhookDeliveries = mockWebhookApis({ detailDeliveries: [failedDelivery] });
    const router = renderPage('/integrations/webhooks?webhookId=7');

    await screen.findByRole('dialog', { name: /Production events · 投递记录/ });
    await selectOption(user, '按状态筛选', '已放弃');

    await waitFor(() => expect(router.state.location.search).toContain('deliveryStatus=dead'));
    await waitFor(() => expect(webhookDeliveries).toHaveBeenCalledWith({
      webhookId: webhook.id,
      status: 'dead',
      eventType: undefined,
      limit: 200
    }));

    await selectOption(user, '按事件筛选', '失败');

    await waitFor(() => {
      const params = new URLSearchParams(router.state.location.search);
      expect(params.get('webhookId')).toBe('7');
      expect(params.get('deliveryStatus')).toBe('dead');
      expect(params.get('deliveryEvent')).toBe('failed');
    });
    await waitFor(() => expect(webhookDeliveries).toHaveBeenCalledWith({
      webhookId: webhook.id,
      status: 'dead',
      eventType: 'failed',
      limit: 200
    }));
  });

  it('keeps detail failures local and retries them without reloading the endpoint list', async () => {
    const user = userEvent.setup();
    let detailAttempts = 0;
    const webhookDeliveries = mockWebhookApis({
      detailResolver: async () => {
        detailAttempts += 1;
        if (detailAttempts === 1) throw new Error('Webhook delivery service unavailable');
        return { deliveries: [failedDelivery] };
      }
    });
    renderPage('/integrations/webhooks?webhookId=7');

    const error = await screen.findByText('Webhook delivery service unavailable');
    const alert = error.closest('.ant-alert');
    expect(alert).not.toBeNull();
    await user.click(within(alert as HTMLElement).getByRole('button', { name: /刷新/ }));

    expect(await screen.findByText('Connection refused by receiver')).not.toBeNull();
    expect(detailAttempts).toBe(2);
    expect(webhookDeliveries.mock.calls.filter(([filters]) => filters.webhookId === webhook.id)).toHaveLength(2);
    expect(api.webhooks).toHaveBeenCalledTimes(1);
  });

  it('closes back to the list without leaving a detail entry behind the list', async () => {
    const user = userEvent.setup();
    mockWebhookApis({ detailDeliveries: [failedDelivery] });
    const router = renderPage('/integrations/webhooks', {
      initialEntries: ['/overview', '/integrations/webhooks'],
      initialIndex: 1
    });

    await screen.findByText(webhook.name);
    await user.click(screen.getByRole('button', { name: '查看投递' }));
    const drawer = await screen.findByRole('dialog', { name: /Production events · 投递记录/ });
    await selectOption(user, '按状态筛选', '已放弃');
    await waitFor(() => expect(new URLSearchParams(router.state.location.search).get('deliveryStatus')).toBe('dead'));

    await act(async () => {
      await router.navigate(-1);
    });
    expect(router.state.location.search).toBe('?webhookId=7');
    await act(async () => {
      await router.navigate(1);
    });
    expect(new URLSearchParams(router.state.location.search).get('deliveryStatus')).toBe('dead');

    await user.click(within(drawer).getByRole('button', { name: /close/i }));

    await waitFor(() => expect(router.state.location.pathname + router.state.location.search).toBe('/integrations/webhooks'));
    expect(screen.queryByRole('dialog', { name: /Production events · 投递记录/ })).toBeNull();

    await act(async () => {
      await router.navigate(-1);
    });
    expect(router.state.location.pathname).toBe('/overview');
    expect(router.state.location.search).toBe('');
  });

  it('requires explicit acknowledgement before removing a newly created secret', async () => {
    const user = userEvent.setup();
    mockWebhookApis({ webhooks: [] });
    const fullSecret = 'whsec_only-visible-once';
    vi.spyOn(api, 'createWebhook').mockResolvedValue({
      webhook: { ...webhook, secret: fullSecret }
    });
    renderPage('/integrations/webhooks');

    await screen.findByText('暂无 Webhook。创建后即可接收投递状态回调。');
    await user.click(screen.getAllByRole('button', { name: /新建 Webhook/ })[0]);
    const editor = await screen.findByRole('dialog', { name: '新建 Webhook' });
    await user.type(within(editor).getByLabelText('名称'), webhook.name);
    await user.type(within(editor).getByLabelText('回调 URL'), webhook.url);
    await user.click(within(editor).getByRole('button', { name: '新建 Webhook' }));

    const reveal = await screen.findByRole('dialog', { name: 'Webhook 密钥已创建' });
    expect(within(reveal).getByText(fullSecret)).not.toBeNull();
    expect(reveal.querySelector('.ant-modal-close')).toBeNull();
    await user.keyboard('{Escape}');
    expect(screen.getByRole('dialog', { name: 'Webhook 密钥已创建' })).not.toBeNull();

    await user.click(within(reveal).getByRole('button', { name: /确.*认/ }));
    await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Webhook 密钥已创建' })).toBeNull());
    expect(screen.queryByText(fullSecret)).toBeNull();
  });
});

interface MockWebhookApisOptions {
  webhooks?: Webhook[];
  detailDeliveries?: WebhookDelivery[];
  detailResolver?: (filters: WebhookDeliveryFilters) => Promise<{ deliveries: WebhookDelivery[] }>;
}

function mockWebhookApis({
  webhooks = [webhook],
  detailDeliveries = [],
  detailResolver
}: MockWebhookApisOptions = {}) {
  vi.spyOn(api, 'webhooks').mockResolvedValue({ webhooks });
  vi.spyOn(api, 'domains').mockResolvedValue({ domains: [] });
  vi.spyOn(api, 'inboundMailboxes').mockResolvedValue({ mailboxes: [] });
  return vi.spyOn(api, 'webhookDeliveries').mockImplementation(async (filters = {}) => {
    if (filters.webhookId != null) {
      if (detailResolver) return detailResolver(filters);
      return { deliveries: detailDeliveries };
    }
    return { deliveries: [] };
  });
}

function renderPage(
  path: string,
  history?: { initialEntries: string[]; initialIndex: number }
) {
  const router = createMemoryRouter([
    {
      path: '/integrations/webhooks',
      element: (
        <>
          <Webhooks />
          <LocationProbe />
        </>
      )
    },
    { path: '*', element: <LocationProbe /> }
  ], history || { initialEntries: [path], initialIndex: 0 });

  render(
    <ConfigProvider theme={{ ...mailhubTheme, token: { ...mailhubTheme.token, motion: false } }}>
      <AntApp>
        <I18nProvider>
          <RouterProvider router={router} />
        </I18nProvider>
      </AntApp>
    </ConfigProvider>
  );
  return router;
}

function LocationProbe() {
  const location = useLocation();
  return <div data-testid="location">{location.pathname}{location.search}</div>;
}

async function selectOption(user: ReturnType<typeof userEvent.setup>, label: string, option: string) {
  await user.click(screen.getByRole('combobox', { name: label }));
  await user.click(await screen.findByText(option, { selector: '.ant-select-item-option-content' }));
}

const webhook: Webhook = {
  id: 7,
  userId: 1,
  domainId: null,
  mailboxId: null,
  name: 'Production events',
  url: 'https://hooks.example.test/mailhub',
  secretPrefix: 'whsec_prod',
  events: ['sent', 'failed'],
  enabled: true,
  createdAt: '2026-07-14T00:00:00.000Z',
  updatedAt: '2026-07-14T00:00:00.000Z'
};

const failedDelivery: WebhookDelivery = {
  id: 31,
  webhookId: webhook.id,
  userId: 1,
  sendEventId: 101,
  eventType: 'failed',
  status: 'dead',
  attemptCount: 5,
  lastAttemptAt: '2026-07-14T00:05:00.000Z',
  responseStatus: 503,
  error: 'Connection refused by receiver',
  createdAt: '2026-07-14T00:00:00.000Z'
};
