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 { afterEach, describe, expect, it, vi } from 'vitest';

import { AppContext, type AppContextValue } from '../../src/frontend/app-context';
import { I18nProvider } from '../../src/frontend/i18n/react';
import { api } from '../../src/frontend/services/api';
import { mailhubTheme } from '../../src/frontend/theme';
import type { RuntimeConfig } from '../../src/frontend/types';
import { AdminLayout } from '../../src/layouts/AdminLayout';
import Settings from '../../src/pages/Settings';

describe('Settings navigation guard', () => {
  afterEach(() => vi.restoreAllMocks());

  it('guards sidebar and history navigation, beforeunload, and stops guarding after a successful save', async () => {
    const user = userEvent.setup();
    vi.spyOn(api, 'adminSettings').mockResolvedValue({ settings: runtimeConfig });
    const saveSettings = vi.spyOn(api, 'saveAdminSettings').mockImplementation(async (values) => ({
      settings: { ...runtimeConfig, ...values }
    }));
    const router = renderSettingsRouter();

    let baseUrlInput = await screen.findByLabelText('APP_BASE_URL');
    const cleanUnload = new Event('beforeunload', { cancelable: true });
    window.dispatchEvent(cleanUnload);
    expect(cleanUnload.defaultPrevented).toBe(false);

    await user.clear(baseUrlInput);
    await user.type(baseUrlInput, 'https://changed.example.test');
    const dirtyUnload = new Event('beforeunload', { cancelable: true });
    window.dispatchEvent(dirtyUnload);
    expect(dirtyUnload.defaultPrevented).toBe(true);

    await user.click(screen.getAllByText('发送活动')[0]);
    let dialog = await screen.findByRole('dialog', { name: '放弃未保存的更改？' });
    expect(router.state.location.pathname).toBe('/settings');
    await user.click(within(dialog).getByRole('button', { name: '继续编辑' }));
    await waitFor(() => expect(screen.queryByRole('dialog', { name: '放弃未保存的更改？' })).toBeNull());
    expect(router.state.location.pathname).toBe('/settings');

    await user.click(screen.getAllByText('发送活动')[0]);
    dialog = await screen.findByRole('dialog', { name: '放弃未保存的更改？' });
    await user.click(within(dialog).getByRole('button', { name: '离开页面' }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/activity'));

    await act(async () => {
      await router.navigate(-1);
    });
    baseUrlInput = await screen.findByLabelText('APP_BASE_URL');
    await user.clear(baseUrlInput);
    await user.type(baseUrlInput, 'https://history.example.test');

    await act(async () => {
      await router.navigate(-1);
    });
    dialog = await screen.findByRole('dialog', { name: '放弃未保存的更改？' });
    expect(router.state.location.pathname).toBe('/settings');
    await user.click(within(dialog).getByRole('button', { name: '继续编辑' }));
    await waitFor(() => expect(screen.queryByRole('dialog', { name: '放弃未保存的更改？' })).toBeNull());

    await act(async () => {
      await router.navigate(1);
    });
    dialog = await screen.findByRole('dialog', { name: '放弃未保存的更改？' });
    await user.click(within(dialog).getByRole('button', { name: '离开页面' }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/activity'));

    await act(async () => {
      await router.navigate(-1);
    });
    baseUrlInput = await screen.findByLabelText('APP_BASE_URL');
    await user.clear(baseUrlInput);
    await user.type(baseUrlInput, 'https://saved.example.test');
    const saveButton = screen.getByRole('button', { name: /保存设置/ });
    await user.click(saveButton);
    await waitFor(() => expect(saveSettings).toHaveBeenCalledTimes(1));
    await waitFor(() => expect((saveButton as HTMLButtonElement).disabled).toBe(true));

    const savedUnload = new Event('beforeunload', { cancelable: true });
    window.dispatchEvent(savedUnload);
    expect(savedUnload.defaultPrevented).toBe(false);
    await user.click(screen.getAllByText('概览')[0]);
    await waitFor(() => expect(router.state.location.pathname).toBe('/overview'));
    expect(screen.queryByRole('dialog', { name: '放弃未保存的更改？' })).toBeNull();
  }, 20_000);

  it('blocks a public HTTP base URL until it is changed to HTTPS', async () => {
    const user = userEvent.setup();
    const insecureConfig = { ...runtimeConfig, appBaseUrl: 'http://mail.example.test' };
    vi.spyOn(api, 'adminSettings').mockResolvedValue({ settings: insecureConfig });
    const saveSettings = vi.spyOn(api, 'saveAdminSettings').mockImplementation(async (values) => ({
      settings: { ...insecureConfig, ...values }
    }));
    renderSettingsRouter(insecureConfig);

    expect(await screen.findByText('公开访问地址不安全')).not.toBeNull();
    const input = screen.getByLabelText('APP_BASE_URL');
    await user.clear(input);
    await user.type(input, 'http://public.example.test');
    await user.click(screen.getByRole('button', { name: /保存设置/ }));
    expect((await screen.findAllByText('公网实例必须使用 HTTPS，避免 API 密钥通过明文 HTTP 传输。')).length).toBeGreaterThan(0);
    expect(saveSettings).not.toHaveBeenCalled();

    await user.clear(input);
    await user.type(input, 'https://public.example.test');
    await user.click(screen.getByRole('button', { name: /保存设置/ }));
    await waitFor(() => expect(saveSettings).toHaveBeenCalledTimes(1));
  });

  it('saves the registration approval policy with clear state and helper copy', async () => {
    const user = userEvent.setup();
    vi.spyOn(api, 'adminSettings').mockResolvedValue({ settings: runtimeConfig });
    const saveSettings = vi.spyOn(api, 'saveAdminSettings').mockImplementation(async (values) => ({
      settings: { ...runtimeConfig, ...values }
    }));
    renderSettingsRouter();

    const approvalSwitch = await screen.findByRole('switch', { name: '邮箱验证后需要管理员审核' });
    expect(approvalSwitch.getAttribute('aria-checked')).toBe('false');
    expect(screen.getByText('关闭时，用户验证邮箱后立即启用；开启时，账号进入待审核状态，需管理员批准后才能登录。')).not.toBeNull();

    await user.click(approvalSwitch);
    expect(approvalSwitch.getAttribute('aria-checked')).toBe('true');
    await user.click(screen.getByRole('button', { name: /保存设置/ }));

    await waitFor(() => expect(saveSettings).toHaveBeenCalledWith(expect.objectContaining({
      registrationRequiresApproval: true
    })));
  });
});

function renderSettingsRouter(config = runtimeConfig) {
  const context: AppContextValue = {
    user: {
      id: 1,
      username: 'operator',
      email: 'operator@example.test',
      role: 'admin',
      status: 'active'
    },
    config,
    refreshBootstrap: vi.fn(async () => undefined),
    logout: vi.fn(async () => undefined)
  };
  const router = createMemoryRouter([
    {
      element: <AdminLayout />,
      children: [
        { path: '/settings', element: <Settings /> },
        { path: '*', element: <LocationProbe /> }
      ]
    }
  ], {
    initialEntries: ['/overview', '/settings', '/activity'],
    initialIndex: 1
  });

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

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

const runtimeConfig: RuntimeConfig = {
  appBaseUrl: 'https://mail.example.test',
  mailHostname: 'mail.example.test',
  sendingIp: '192.0.2.10',
  defaultSpfMechanisms: '',
  dmarcPolicy: 'none',
  dmarcRua: '',
  registrationRequiresApproval: false,
  sendRequiresVerified: true,
  engagementTrackingEnabled: true,
  listUnsubscribeMailto: '',
  listUnsubscribeUrl: '',
  listUnsubscribePostEnabled: false,
  feedbackIdEnabled: false,
  reportAbuseTo: '',
  csaComplaintsTo: '',
  bounceAddress: '',
  bounceEnvelopeEnabled: false
};
