import { App as AntApp, ConfigProvider } from 'antd';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import { afterEach, beforeEach, 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 { Analytics, ApiToken, RuntimeConfig, WebhookDelivery } from '../../src/frontend/types';
import Dashboard from '../../src/pages/Dashboard';

vi.mock('../../src/pages/DashboardCharts', () => ({
  default: () => <div data-testid="dashboard-charts" />
}));

describe('Dashboard operational states', () => {
  beforeEach(() => {
    window.localStorage.removeItem('mailhub.locale');
  });

  afterEach(() => vi.restoreAllMocks());

  it('keeps dependency failures visible without reporting a missing credential or all-clear state', async () => {
    const user = userEvent.setup();
    mockCoreApis();
    vi.spyOn(api, 'smtpCredential')
      .mockRejectedValueOnce(new Error('SMTP timeout'))
      .mockResolvedValue({ credential: null });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [sendToken] });
    const deliveries = vi.spyOn(api, 'webhookDeliveries')
      .mockRejectedValueOnce(new Error('Webhook timeout'))
      .mockResolvedValue({ deliveries: [] });

    renderDashboard();

    expect(await screen.findByText('部分运维状态获取失败')).toBeTruthy();
    expect(screen.getByText(/SMTP 凭据.*SMTP timeout/)).toBeTruthy();
    expect(screen.getByText(/Webhook 失败投递.*Webhook timeout/)).toBeTruthy();
    expect(screen.queryByText('尚未创建可用的发送凭据')).toBeNull();
    expect(screen.queryByText('状态正常')).toBeNull();
    expect(screen.getByText('状态未知')).toBeTruthy();

    await user.click(screen.getByRole('button', { name: /重\s*试/ }));

    await waitFor(() => expect(screen.queryByText('部分运维状态获取失败')).toBeNull());
    expect(await screen.findByText('状态正常')).toBeTruthy();
    expect(deliveries).toHaveBeenCalledTimes(2);
  });

  it('accepts SMTP or a send-scoped API key and exposes both setup entrances', async () => {
    const user = userEvent.setup();
    mockCoreApis();
    vi.spyOn(api, 'smtpCredential').mockResolvedValue({ credential: null });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [] });
    vi.spyOn(api, 'webhookDeliveries').mockResolvedValue({ deliveries: [] });

    const router = renderDashboard();

    expect(await screen.findByText('尚未创建可用的发送凭据')).toBeTruthy();
    expect(screen.getByRole('button', { name: /配置 SMTP/ })).toBeTruthy();
    expect(screen.getByRole('button', { name: /创建 API 密钥/ })).toBeTruthy();
    expect(screen.getByRole('button', { name: 'SMTP' })).toBeTruthy();
    expect(screen.getByRole('button', { name: 'API 密钥' })).toBeTruthy();

    await user.click(screen.getByRole('button', { name: /创建 API 密钥/ }));
    await waitFor(() => expect(router.state.location.pathname).toBe('/integrations/api-keys'));
  });

  it('does not flag credentials as missing when an active send-scoped API key exists', async () => {
    mockCoreApis();
    vi.spyOn(api, 'smtpCredential').mockResolvedValue({ credential: null });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [sendToken] });
    vi.spyOn(api, 'webhookDeliveries').mockResolvedValue({ deliveries: [] });

    renderDashboard();

    expect(await screen.findByText('状态正常')).toBeTruthy();
    expect(screen.queryByText('尚未创建可用的发送凭据')).toBeNull();
  });

  it('explains the deployment-level ADMIN_PASSWORD change without linking to system settings', async () => {
    mockHealthyDependencies();

    renderDashboard({ usingDefaultAdminPassword: true });

    const warning = (await screen.findByText('当前仍在使用默认管理员密码')).closest('.ant-alert');
    expect(warning).not.toBeNull();
    expect(within(warning as HTMLElement).getByText(/ADMIN_PASSWORD/)).toBeTruthy();
    expect(within(warning as HTMLElement).getByText(/重启 MailHub/)).toBeTruthy();
    expect(within(warning as HTMLElement).queryByRole('link')).toBeNull();
  });

  it('links a failed webhook warning to that webhook dead-delivery filter', async () => {
    const user = userEvent.setup();
    mockCoreApis();
    vi.spyOn(api, 'smtpCredential').mockResolvedValue({ credential: { username: 'smtp-user', passwordSet: true } });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [] });
    const deliveries = vi.spyOn(api, 'webhookDeliveries').mockResolvedValue({ deliveries: [deadDelivery] });

    const router = renderDashboard();

    await user.click(await screen.findByRole('button', { name: /检查 Webhooks/ }));

    await waitFor(() => expect(router.state.location.pathname).toBe('/integrations/webhooks'));
    const params = new URLSearchParams(router.state.location.search);
    expect(params.get('webhookId')).toBe('42');
    expect(params.get('deliveryStatus')).toBe('dead');
    expect(deliveries).toHaveBeenCalledWith({ status: 'dead', limit: 5 });
  });

  it('never labels analytics from the previous range as the newly selected range after a failed refresh', async () => {
    const user = userEvent.setup();
    const previousRangeAnalytics: Analytics = {
      ...analytics,
      summary: { ...analytics.summary, total: 712345 }
    };
    const analyticsRequest = vi.spyOn(api, 'analytics')
      .mockResolvedValueOnce({ analytics: previousRangeAnalytics })
      .mockRejectedValueOnce(new Error('Analytics service unavailable'));
    vi.spyOn(api, 'domains').mockResolvedValue({ domains: [] });
    vi.spyOn(api, 'smtpCredential').mockResolvedValue({ credential: { username: 'smtp-user', passwordSet: true } });
    vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [] });
    vi.spyOn(api, 'webhookDeliveries').mockResolvedValue({ deliveries: [] });

    renderDashboard();

    expect(await screen.findByText('712345')).toBeTruthy();
    await user.click(screen.getByText('30 天'));

    expect(await screen.findByText('概览加载失败')).toBeTruthy();
    expect(screen.getByText(/投递统计.*Analytics service unavailable/)).toBeTruthy();
    expect(screen.queryByText('712345')).toBeNull();
    expect(analyticsRequest).toHaveBeenLastCalledWith(30);
  });
});

function renderDashboard(configPatch: Partial<RuntimeConfig> = {}) {
  const router = createMemoryRouter([
    { path: '/overview', element: <Dashboard /> },
    { path: '*', element: <div>Destination</div> }
  ], { initialEntries: ['/overview'] });
  const context: AppContextValue = {
    user: { id: 1, username: 'admin', email: 'admin@example.test', role: 'admin', status: 'active' },
    config: { ...runtimeConfig, ...configPatch },
    refreshBootstrap: vi.fn(async () => undefined),
    logout: vi.fn(async () => undefined)
  };
  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 mockCoreApis() {
  vi.spyOn(api, 'analytics').mockResolvedValue({ analytics });
  vi.spyOn(api, 'domains').mockResolvedValue({ domains: [] });
}

function mockHealthyDependencies() {
  mockCoreApis();
  vi.spyOn(api, 'smtpCredential').mockResolvedValue({ credential: { username: 'smtp-user', passwordSet: true } });
  vi.spyOn(api, 'apiTokens').mockResolvedValue({ tokens: [] });
  vi.spyOn(api, 'webhookDeliveries').mockResolvedValue({ deliveries: [] });
}

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
};

const analytics: Analytics = {
  windowDays: 7,
  summary: {
    total: 0,
    submitted: 0,
    queued: 0,
    failed: 0,
    accepted: 0,
    delivered: 0,
    pending: 0,
    deferred: 0,
    bounced: 0,
    terminalFailed: 0,
    recipients: 0,
    today: 0,
    last7Days: 0,
    successRate: 0,
    acceptanceRate: 0,
    deliveryRate: 0,
    failureRate: 0,
    domains: 0,
    verifiedDomains: 0
  },
  deliveryFunnel: [],
  engagement: {
    trackedDelivered: 0,
    totalOpens: 0,
    uniqueOpens: 0,
    proxyOpens: 0,
    totalClicks: 0,
    uniqueClicks: 0,
    scannerEvents: 0,
    openRate: 0,
    clickRate: 0,
    clickToOpenRate: 0
  },
  engagementByDay: [],
  topLinks: [],
  byDay: [],
  byDomain: [],
  byStatus: [],
  hourly: [],
  failureReasons: [],
  recentFailures: []
};

const sendToken: ApiToken = {
  id: 7,
  name: 'sender',
  tokenPrefix: 'mh_sender',
  tokenRecoverable: false,
  scopes: ['send'],
  mailboxAccess: 'owner',
  mailboxIds: [],
  status: 'active',
  createdAt: '2026-07-15T00:00:00.000Z'
};

const deadDelivery: WebhookDelivery = {
  id: 10,
  webhookId: 42,
  userId: 1,
  sendEventId: 99,
  eventType: 'failed',
  status: 'dead',
  attemptCount: 8,
  error: 'connection refused',
  createdAt: '2026-07-15T00:00:00.000Z'
};
