import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Dashboard } from './Dashboard';

const apiMock = vi.hoisted(() => ({
  getDashboardOverview: vi.fn(),
  cancelTask: vi.fn(),
  retryTask: vi.fn(),
  getTask: vi.fn(),
}));

const wsMock = vi.hoisted(() => {
  const state = {
    callback: undefined as undefined | ((data: any) => void),
    statusCallback: undefined as undefined | ((status: any) => void),
    connected: false,
  };
  return {
    state,
    progressWS: {
      connect: vi.fn(() => { state.connected = true; }),
      disconnect: vi.fn(() => { state.connected = false; }),
      isConnected: vi.fn(() => state.connected),
      onProgress: vi.fn((cb: (data: any) => void) => {
        state.callback = cb;
        return vi.fn();
      }),
      onStatus: vi.fn((cb: (status: any) => void) => {
        state.statusCallback = cb;
        cb(state.connected ? 'connected' : 'disconnected');
        return vi.fn();
      }),
    },
  };
});

vi.mock('../lib/websocket', () => ({
  progressWS: wsMock.progressWS,
}));

vi.mock('../lib/api', () => ({
  getDashboardOverview: apiMock.getDashboardOverview,
  cancelTask: apiMock.cancelTask,
  retryTask: apiMock.retryTask,
  getTask: apiMock.getTask,
}));

describe('Dashboard', () => {
  beforeEach(() => {
    vi.useRealTimers();
    vi.clearAllMocks();
    wsMock.state.connected = false;
    wsMock.state.callback = undefined;
    wsMock.state.statusCallback = undefined;
    apiMock.getDashboardOverview.mockResolvedValue({
      summary: {
        project_count: 2,
        parsing_task_count: 1,
        annotation_count: 5,
        frame_count: 100,
        template_count: 3,
        system_load_percent: 12,
      },
      tasks: [
        {
          id: 'project-1',
          project_id: 1,
          name: '真实项目.mp4',
          progress: 60,
          status: 'pending',
          raw_status: 'running',
          error: null,
          frame_count: 10,
          updated_at: '2026-05-01T00:00:00Z',
        },
      ],
      activity: [
        {
          id: 'activity-1',
          kind: 'project',
          time: '2026-05-01T00:00:00Z',
          message: '项目状态: pending',
          project: '真实项目.mp4',
        },
      ],
    });
  });

  it('loads dashboard stats, tasks, and activity from the backend overview endpoint', async () => {
    render(<Dashboard />);

    await waitFor(() => expect(apiMock.getDashboardOverview).toHaveBeenCalled());
    expect(screen.getByText('项目总数')).toBeInTheDocument();
    expect(screen.getByText('已存标注')).toBeInTheDocument();
    expect(screen.getByText('真实项目.mp4')).toBeInTheDocument();
    expect(screen.getByText('项目状态: pending')).toBeInTheDocument();
    expect(screen.queryByText('City_Driving_Dataset_004.mp4')).not.toBeInTheDocument();
  });

  it('keeps a recently completed task visible in the progress panel', async () => {
    apiMock.getDashboardOverview.mockResolvedValueOnce({
      summary: {
        project_count: 1,
        parsing_task_count: 0,
        annotation_count: 0,
        frame_count: 120,
        template_count: 1,
        system_load_percent: 8,
      },
      tasks: [
        {
          id: 'task-20',
          task_id: 20,
          project_id: 1,
          name: 'completed.mp4',
          progress: 100,
          status: '解析完成',
          raw_status: 'success',
          error: null,
          frame_count: 120,
          updated_at: '2026-05-01T00:00:00Z',
        },
      ],
      activity: [],
    });

    render(<Dashboard />);

    expect(await screen.findByText('任务进度 (当前 / 最近)')).toBeInTheDocument();
    expect(screen.getByText('completed.mp4')).toBeInTheDocument();
    expect(screen.getByText('100%')).toBeInTheDocument();
    expect(screen.getByText('解析完成')).toBeInTheDocument();
    expect(screen.queryByText(/当前无处理任务/)).not.toBeInTheDocument();
  });

  it('connects to the progress stream and updates progress tasks', async () => {
    render(<Dashboard />);

    await waitFor(() => expect(wsMock.progressWS.connect).toHaveBeenCalled());

    act(() => {
      wsMock.state.callback?.({
        type: 'progress',
        taskId: 'task-1',
        projectName: 'demo.mp4',
        progress: 44,
        status: '正在截取帧',
      });
    });

    expect(await screen.findByText('demo.mp4')).toBeInTheDocument();
    expect(screen.getByText('44%')).toBeInTheDocument();
  });

  it('updates the websocket badge from connection status callbacks', async () => {
    render(<Dashboard />);

    await waitFor(() => expect(wsMock.progressWS.onStatus).toHaveBeenCalled());
    expect(screen.getByText('WebSocket 断开')).toBeInTheDocument();

    act(() => {
      wsMock.state.connected = true;
      wsMock.state.statusCallback?.('connected');
    });

    expect(screen.getByText('WebSocket 已连接')).toBeInTheDocument();
  });

  it('adds activity logs for complete and status messages', async () => {
    render(<Dashboard />);

    act(() => {
      wsMock.state.callback?.({ type: 'status', message: 'Progress stream active' });
      wsMock.state.callback?.({ type: 'complete', taskId: '1', filename: 'done.mp4' });
    });

    await waitFor(() => expect(screen.getByText('Progress stream active')).toBeInTheDocument());
    expect(screen.getByText('解析完成: done.mp4')).toBeInTheDocument();
  });

  it('cancels, retries, and opens task failure details', async () => {
    apiMock.getDashboardOverview.mockResolvedValueOnce({
      summary: {
        project_count: 1,
        parsing_task_count: 1,
        annotation_count: 0,
        frame_count: 0,
        template_count: 0,
        system_load_percent: 5,
      },
      tasks: [
        {
          id: 'task-10',
          task_id: 10,
          project_id: 1,
          name: 'running.mp4',
          progress: 30,
          status: '正在下载媒体文件',
          raw_status: 'running',
          error: null,
          frame_count: 0,
          updated_at: '2026-05-01T00:00:00Z',
        },
        {
          id: 'task-11',
          task_id: 11,
          project_id: 1,
          name: 'failed.mp4',
          progress: 100,
          status: '解析失败',
          raw_status: 'failed',
          error: 'ffmpeg failed',
          frame_count: 0,
          updated_at: '2026-05-01T00:01:00Z',
        },
      ],
      activity: [],
    });
    apiMock.cancelTask.mockResolvedValueOnce({
      id: 10,
      task_type: 'parse_video',
      status: 'cancelled',
      progress: 100,
      message: '任务已取消',
      project_id: 1,
      error: 'Cancelled by user',
      result: null,
      payload: { source_type: 'video' },
      created_at: 'created',
      updated_at: 'updated',
    });
    apiMock.retryTask.mockResolvedValueOnce({
      id: 12,
      task_type: 'parse_video',
      status: 'queued',
      progress: 0,
      message: '重试任务已入队（源任务 #11）',
      project_id: 1,
      error: null,
      result: null,
      payload: { source_type: 'video', retry_of: 11 },
      created_at: 'created',
      updated_at: 'updated',
    });
    apiMock.getTask.mockResolvedValueOnce({
      id: 11,
      task_type: 'parse_video',
      status: 'failed',
      progress: 100,
      message: '解析失败',
      project_id: 1,
      celery_task_id: 'celery-11',
      payload: { source_type: 'video' },
      result: null,
      error: 'ffmpeg failed',
      created_at: 'created',
      started_at: 'started',
      finished_at: 'finished',
      updated_at: 'updated',
    });

    render(<Dashboard />);

    await screen.findByText('running.mp4');
    fireEvent.click(screen.getByRole('button', { name: '取消' }));
    await waitFor(() => expect(apiMock.cancelTask).toHaveBeenCalledWith(10));

    fireEvent.click(screen.getAllByRole('button', { name: '详情' })[1]);
    await waitFor(() => expect(apiMock.getTask).toHaveBeenCalledWith(11));
    expect(await screen.findByText('任务详情 #11')).toBeInTheDocument();
    expect(screen.getByText('ffmpeg failed')).toBeInTheDocument();

    fireEvent.click(screen.getAllByRole('button', { name: '重试' })[1]);
    await waitFor(() => expect(apiMock.retryTask).toHaveBeenCalledWith(11));
  });
});
