import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { resetStore } from '../test/storeTestUtils';
import { useStore } from '../store/useStore';
import { OntologyInspector } from './OntologyInspector';

const apiMock = vi.hoisted(() => ({
  analyzeMask: vi.fn(),
  smoothMaskGeometry: vi.fn(),
  updateTemplate: vi.fn(),
}));

vi.mock('../lib/api', () => ({
  analyzeMask: apiMock.analyzeMask,
  smoothMaskGeometry: apiMock.smoothMaskGeometry,
  updateTemplate: apiMock.updateTemplate,
}));

describe('OntologyInspector', () => {
  beforeEach(() => {
    resetStore();
    vi.clearAllMocks();
    apiMock.analyzeMask.mockResolvedValue({
      confidence: 0.82,
      confidence_source: 'model_score',
      topology_anchor_count: 4,
      topology_anchors: [],
      area: 0.1,
      bbox: [0, 0, 0.1, 0.1],
      source: 'sam2.1_hiera_tiny',
      message: '已读取后端几何属性',
    });
    apiMock.smoothMaskGeometry.mockResolvedValue({
      polygons: [[[0.12, 0.12], [0.28, 0.12], [0.28, 0.28], [0.12, 0.28]]],
      pathData: 'M 12 12 L 28 12 L 28 28 L 12 28 Z',
      segmentation: [[12, 12, 28, 12, 28, 28, 12, 28]],
      bbox: [12, 12, 16, 16],
      area: 256,
      topology_anchor_count: 4,
      topology_anchors: [],
      smoothing: { strength: 35, method: 'chaikin' },
      message: '已应用边缘平滑强度 35',
    });
    useStore.setState({
      templates: [
        {
          id: 't1',
          name: '腹腔镜模板',
          classes: [
            { id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 20, category: '器官' },
            { id: 'c2', name: '肝脏', color: '#00ff00', zIndex: 10, category: '器官' },
          ],
          rules: [],
        },
      ],
    });
  });

  it('shows template classes and changes the active template', () => {
    const { container } = render(<OntologyInspector />);

    const templateSelect = screen.getByRole('combobox');
    expect(container.querySelector('.seg-scrollbar')).toBeInTheDocument();
    expect(screen.queryByText('本体论与属性分类管理树')).not.toBeInTheDocument();
    fireEvent.change(templateSelect, { target: { value: 't1' } });

    expect(useStore.getState().activeTemplateId).toBe('t1');
    expect(screen.getByText('胆囊')).toBeInTheDocument();
    expect(screen.getByText('肝脏')).toBeInTheDocument();
    expect(screen.getByText('maskid:1')).toBeInTheDocument();
    expect(screen.getByText('maskid:2')).toBeInTheDocument();
    expect(screen.queryByText(/z:/)).not.toBeInTheDocument();
  });

  it('adjusts workspace mask opacity from above the semantic tree', () => {
    render(<OntologyInspector />);

    fireEvent.change(screen.getByLabelText('遮罩透明度'), { target: { value: '35' } });

    expect(useStore.getState().maskPreviewOpacity).toBe(35);
    expect(screen.getByText('35%')).toBeInTheDocument();
  });

  it('focuses the matching semantic class when a mask is selected', async () => {
    if (!HTMLElement.prototype.scrollIntoView) {
      HTMLElement.prototype.scrollIntoView = vi.fn();
    }
    useStore.setState({
      masks: [{
        id: 'm1',
        frameId: 'frame-1',
        pathData: 'M 0 0 Z',
        label: '肝脏',
        color: '#00ff00',
        classId: 'c2',
        className: '肝脏',
      }],
      selectedMaskIds: ['m1'],
    });

    render(<OntologyInspector />);

    const liverButton = screen.getByRole('button', { name: /肝脏/ });
    await waitFor(() => expect(useStore.getState().activeClassId).toBe('c2'));
    expect(liverButton).toHaveAttribute('aria-current', 'true');
    expect(document.activeElement).toBe(liverButton);
  });

  it('selects a concrete class for subsequent masks', () => {
    render(<OntologyInspector />);

    fireEvent.click(screen.getByText('胆囊'));

    expect(useStore.getState().activeClassId).toBe('c1');
    expect(useStore.getState().activeClass).toEqual(expect.objectContaining({
      id: 'c1',
      name: '胆囊',
      zIndex: 20,
    }));
  });

  it('applies the selected class to currently selected masks', () => {
    useStore.setState({
      selectedMaskIds: ['m1'],
      masks: [
        {
          id: 'm2',
          frameId: 'frame-1',
          pathData: 'M 10 10 Z',
          label: '未选区域',
          color: '#ffffff',
          saveStatus: 'draft',
        },
        {
          id: 'm1',
          annotationId: '99',
          frameId: 'frame-1',
          pathData: 'M 0 0 Z',
          label: '旧标签',
          color: '#06b6d4',
          saveStatus: 'saved',
          saved: true,
        },
      ],
    });

    render(<OntologyInspector />);
    fireEvent.click(screen.getByText('肝脏'));

    expect(useStore.getState().activeClassId).toBe('c2');
    expect(useStore.getState().masks.map((mask) => mask.id)).toEqual(['m2', 'm1']);
    expect(useStore.getState().masks[1]).toEqual(expect.objectContaining({
      templateId: 't1',
      classId: 'c2',
      className: '肝脏',
      classZIndex: 10,
      classMaskId: 2,
      label: '肝脏',
      color: '#00ff00',
      saveStatus: 'dirty',
      saved: false,
    }));
    expect(screen.queryByText('当前选中区域:')).not.toBeInTheDocument();
  });

  it('applies class changes to the same propagation chain across frames', () => {
    useStore.setState({
      selectedMaskIds: ['annotation-10'],
      masks: [
        {
          id: 'annotation-10',
          annotationId: '10',
          frameId: 'frame-1',
          pathData: 'M 0 0 Z',
          label: '旧标签',
          color: '#06b6d4',
          saveStatus: 'saved',
          saved: true,
        },
        {
          id: 'annotation-11',
          annotationId: '11',
          frameId: 'frame-2',
          pathData: 'M 1 1 Z',
          label: '旧传播标签',
          color: '#06b6d4',
          metadata: {
            source_annotation_id: 10,
            source_mask_id: 'annotation-10',
            propagation_seed_key: 'annotation:10',
          },
          saveStatus: 'saved',
          saved: true,
        },
        {
          id: 'annotation-99',
          annotationId: '99',
          frameId: 'frame-3',
          pathData: 'M 2 2 Z',
          label: '无关区域',
          color: '#ffffff',
          metadata: { source_annotation_id: 99 },
          saveStatus: 'saved',
          saved: true,
        },
      ],
    });

    render(<OntologyInspector />);
    fireEvent.click(screen.getByText('肝脏'));

    const updated = useStore.getState().masks;
    expect(updated.find((mask) => mask.id === 'annotation-10')).toEqual(expect.objectContaining({
      classId: 'c2',
      className: '肝脏',
      classMaskId: 2,
      label: '肝脏',
      color: '#00ff00',
      saveStatus: 'dirty',
      saved: false,
    }));
    expect(updated.find((mask) => mask.id === 'annotation-11')).toEqual(expect.objectContaining({
      classId: 'c2',
      className: '肝脏',
      classMaskId: 2,
      label: '肝脏',
      color: '#00ff00',
      saveStatus: 'dirty',
      saved: false,
    }));
    expect(updated.find((mask) => mask.id === 'annotation-99')).toEqual(expect.objectContaining({
      label: '无关区域',
      color: '#ffffff',
      saveStatus: 'saved',
      saved: true,
    }));
  });

  it('persists custom classes to the active backend template', async () => {
    apiMock.updateTemplate.mockResolvedValueOnce({
      id: 't1',
      name: '腹腔镜模板',
      classes: [
        { id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 20, category: '器官' },
        { id: 'c2', name: '肝脏', color: '#00ff00', zIndex: 10, category: '器官' },
        { id: 'custom-1', name: '新局部分类', color: '#06b6d4', zIndex: 30, category: '自定义' },
      ],
      rules: [],
    });

    render(<OntologyInspector />);
    fireEvent.change(screen.getByRole('combobox'), { target: { value: 't1' } });
    const customSection = screen.getByText('自定义分类').parentElement!;
    fireEvent.click(within(customSection).getByRole('button'));
    fireEvent.change(screen.getByPlaceholderText('分类名称'), { target: { value: '新局部分类' } });
    fireEvent.keyDown(screen.getByPlaceholderText('分类名称'), { key: 'Enter' });

    expect(await screen.findByText('自定义分类已保存到后端模板')).toBeInTheDocument();
    expect(apiMock.updateTemplate).toHaveBeenCalledWith('t1', expect.objectContaining({
      classes: expect.arrayContaining([expect.objectContaining({ name: '新局部分类', category: '自定义' })]),
    }));
    expect(useStore.getState().activeClass).toEqual(expect.objectContaining({ name: '新局部分类' }));
    expect(useStore.getState().templates[0].classes).toHaveLength(3);
  });

  it('persists dragged semantic class order as layer priority without changing maskid', async () => {
    apiMock.updateTemplate.mockResolvedValueOnce({
      id: 't1',
      name: '腹腔镜模板',
      classes: [
        { id: 'c2', name: '肝脏', color: '#00ff00', zIndex: 20, maskId: 2, category: '器官' },
        { id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 10, maskId: 1, category: '器官' },
      ],
      rules: [],
    });
    useStore.setState({
      masks: [{
        id: 'm-liver',
        annotationId: '42',
        frameId: 'frame-1',
        classId: 'c2',
        className: '肝脏',
        classZIndex: 10,
        pathData: 'M 0 0 Z',
        label: '肝脏',
        color: '#00ff00',
        saveStatus: 'saved',
        saved: true,
      }],
    });

    render(<OntologyInspector />);
    const liverButton = screen.getByRole('button', { name: /肝脏/ });
    const gallbladderButton = screen.getByRole('button', { name: /胆囊/ });
    const dataTransfer = {
      effectAllowed: '',
      dropEffect: '',
      setData: vi.fn(),
      getData: vi.fn(() => 'c2'),
    };

    fireEvent.dragStart(liverButton, { dataTransfer });
    fireEvent.dragOver(gallbladderButton, { dataTransfer });
    fireEvent.drop(gallbladderButton, { dataTransfer });

    await waitFor(() => expect(apiMock.updateTemplate).toHaveBeenCalledWith('t1', expect.objectContaining({
      classes: [
        expect.objectContaining({ id: 'c2', zIndex: 20, maskId: 2 }),
        expect.objectContaining({ id: 'c1', zIndex: 10, maskId: 1 }),
      ],
    })));
    expect(useStore.getState().masks[0]).toEqual(expect.objectContaining({
      classZIndex: 20,
      saveStatus: 'dirty',
      saved: false,
    }));
  });

  it('loads selected mask properties from the backend analyzer', async () => {
    useStore.setState({
      frames: [{ id: 'frame-1', projectId: 'p1', index: 0, url: '/1.jpg', width: 100, height: 100 }],
      activeClass: { id: 'c3', name: '肿瘤', color: '#f97316', zIndex: 30 },
      activeClassId: 'c3',
      selectedMaskIds: ['m1'],
      masks: [
        {
          id: 'm1',
          frameId: 'frame-1',
          pathData: 'M 0 0 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[10, 10, 20, 10, 20, 20]],
          metadata: { source: 'sam2.1_hiera_tiny', score: 0.82 },
        },
      ],
    });

    render(<OntologyInspector />);

    expect(await screen.findByText('4 节点')).toBeInTheDocument();
    expect(screen.getAllByText('胆囊')).toHaveLength(2);
    expect(screen.queryByText('肿瘤')).not.toBeInTheDocument();
    expect(screen.queryByText('后端模型置信度')).not.toBeInTheDocument();
    expect(screen.queryByText('0.8200')).not.toBeInTheDocument();
    expect(screen.getByText('4 节点')).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: '重新提取拓扑锚点' })).not.toBeInTheDocument();
    expect(apiMock.analyzeMask).toHaveBeenLastCalledWith(
      expect.objectContaining({ id: 'm1' }),
      expect.objectContaining({ id: 'frame-1' }),
    );
  });

  it('ignores aborted mask analysis requests without showing an error', async () => {
    const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
    apiMock.analyzeMask.mockRejectedValueOnce({ code: 'ECONNABORTED', message: 'Request aborted' });
    useStore.setState({
      frames: [{ id: 'frame-1', projectId: 'p1', index: 0, url: '/1.jpg', width: 100, height: 100 }],
      selectedMaskIds: ['m1'],
      masks: [
        {
          id: 'm1',
          frameId: 'frame-1',
          pathData: 'M 0 0 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[10, 10, 20, 10, 20, 20]],
        },
      ],
    });

    render(<OntologyInspector />);

    await waitFor(() => expect(apiMock.analyzeMask).toHaveBeenCalled());
    await waitFor(() => expect(screen.queryByText('后端属性读取失败')).not.toBeInTheDocument());
    expect(consoleError).not.toHaveBeenCalled();
    consoleError.mockRestore();
  });

  it('previews backend edge smoothing while moving the slider without marking the mask dirty', async () => {
    useStore.setState({
      frames: [{ id: 'frame-1', projectId: 'p1', index: 0, url: '/1.jpg', width: 100, height: 100 }],
      selectedMaskIds: ['m1'],
      masks: [
        {
          id: 'm1',
          annotationId: '10',
          frameId: 'frame-1',
          pathData: 'M 10 10 L 30 10 L 30 30 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[10, 10, 30, 10, 30, 30]],
          saveStatus: 'saved',
          saved: true,
        },
      ],
    });

    render(<OntologyInspector />);

    fireEvent.change(screen.getByLabelText('边缘平滑强度'), { target: { value: '35' } });

    await waitFor(() => expect(apiMock.smoothMaskGeometry).toHaveBeenCalledWith(
      expect.objectContaining({ id: 'm1' }),
      expect.objectContaining({ id: 'frame-1' }),
      35,
    ));
    await waitFor(() => expect(useStore.getState().masks[0]).toEqual(expect.objectContaining({
      pathData: 'M 12 12 L 28 12 L 28 28 L 12 28 Z',
      segmentation: [[12, 12, 28, 12, 28, 28, 12, 28]],
      bbox: [12, 12, 16, 16],
      area: 256,
      saveStatus: 'saved',
      saved: true,
      metadata: { geometry_smoothing_preview: { strength: 35, method: 'chaikin' } },
    })));
    expect(screen.getByText('已应用边缘平滑强度 35，预览中，点击应用后写入当前 mask。')).toBeInTheDocument();
  });

  it('debounces backend edge smoothing preview while dragging the slider', async () => {
    vi.useFakeTimers();
    try {
      useStore.setState({
        frames: [{ id: 'frame-1', projectId: 'p1', index: 0, url: '/1.jpg', width: 100, height: 100 }],
        selectedMaskIds: ['m1'],
        masks: [
          {
            id: 'm1',
            annotationId: '10',
            frameId: 'frame-1',
            pathData: 'M 10 10 L 30 10 L 30 30 Z',
            label: '胆囊',
            color: '#ff0000',
            segmentation: [[10, 10, 30, 10, 30, 30]],
            saveStatus: 'saved',
            saved: true,
          },
        ],
      });

      render(<OntologyInspector />);

      fireEvent.change(screen.getByLabelText('边缘平滑强度'), { target: { value: '15' } });
      fireEvent.change(screen.getByLabelText('边缘平滑强度'), { target: { value: '25' } });
      fireEvent.change(screen.getByLabelText('边缘平滑强度'), { target: { value: '35' } });

      expect(screen.getByText('正在等待停止拖动后生成边缘平滑预览...')).toBeInTheDocument();
      expect(apiMock.smoothMaskGeometry).not.toHaveBeenCalled();

      act(() => {
        vi.advanceTimersByTime(219);
      });
      expect(apiMock.smoothMaskGeometry).not.toHaveBeenCalled();

      await act(async () => {
        vi.advanceTimersByTime(1);
        await Promise.resolve();
      });

      expect(apiMock.smoothMaskGeometry).toHaveBeenCalledTimes(1);
      expect(apiMock.smoothMaskGeometry).toHaveBeenCalledWith(
        expect.objectContaining({ id: 'm1' }),
        expect.objectContaining({ id: 'frame-1' }),
        35,
      );
    } finally {
      vi.useRealTimers();
    }
  });

  it('applies a previewed edge smoothing result to the selected mask and marks it dirty', async () => {
    useStore.setState({
      frames: [{ id: 'frame-1', projectId: 'p1', index: 0, url: '/1.jpg', width: 100, height: 100 }],
      selectedMaskIds: ['m1'],
      masks: [
        {
          id: 'm1',
          annotationId: '10',
          frameId: 'frame-1',
          pathData: 'M 10 10 L 30 10 L 30 30 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[10, 10, 30, 10, 30, 30]],
          saveStatus: 'saved',
          saved: true,
        },
      ],
    });

    render(<OntologyInspector />);

    fireEvent.change(screen.getByLabelText('边缘平滑强度'), { target: { value: '35' } });
    await waitFor(() => expect(screen.getByRole('button', { name: '应用边缘平滑' })).not.toBeDisabled());
    fireEvent.click(screen.getByRole('button', { name: '应用边缘平滑' }));

    await waitFor(() => expect(useStore.getState().masks[0]).toEqual(expect.objectContaining({
      pathData: 'M 12 12 L 28 12 L 28 28 L 12 28 Z',
      segmentation: [[12, 12, 28, 12, 28, 28, 12, 28]],
      bbox: [12, 12, 16, 16],
      area: 256,
      saveStatus: 'dirty',
      saved: false,
    })));
    expect(useStore.getState().masks[0].metadata?.geometry_smoothing).toBeUndefined();
    expect(apiMock.smoothMaskGeometry).toHaveBeenCalledTimes(1);
    expect(screen.getByText('0%')).toBeInTheDocument();
    expect(screen.getByText('已应用边缘平滑强度 35，已变为新的 mask，强度已重置为 0，请保存后生效')).toBeInTheDocument();
  });

  it('applies smoothing to linked propagation masks as one undoable geometry edit', async () => {
    useStore.setState({
      frames: [
        { id: 'frame-0', projectId: 'p1', index: 0, url: '/0.jpg', width: 100, height: 100 },
        { id: 'frame-1', projectId: 'p1', index: 1, url: '/1.jpg', width: 100, height: 100 },
        { id: 'frame-2', projectId: 'p1', index: 2, url: '/2.jpg', width: 100, height: 100 },
      ],
      selectedMaskIds: ['seed-mask'],
      masks: [
        {
          id: 'seed-mask',
          annotationId: '10',
          frameId: 'frame-1',
          pathData: 'M 10 10 L 30 10 L 30 30 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[10, 10, 30, 10, 30, 30]],
          saveStatus: 'saved',
          saved: true,
        },
        {
          id: 'prop-backward',
          annotationId: '11',
          frameId: 'frame-0',
          pathData: 'M 11 11 L 31 11 L 31 31 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[11, 11, 31, 11, 31, 31]],
          saveStatus: 'saved',
          saved: true,
          metadata: { source_annotation_id: 10, source_mask_id: 'annotation-10', propagated_from_frame_id: 10 },
        },
        {
          id: 'prop-forward',
          annotationId: '12',
          frameId: 'frame-2',
          pathData: 'M 12 12 L 32 12 L 32 32 Z',
          label: '胆囊',
          color: '#ff0000',
          segmentation: [[12, 12, 32, 12, 32, 32]],
          saveStatus: 'saved',
          saved: true,
          metadata: { source_annotation_id: 10, source_mask_id: 'annotation-10', propagated_from_frame_id: 10 },
        },
      ],
    });

    render(<OntologyInspector />);

    fireEvent.change(screen.getByLabelText('边缘平滑强度'), { target: { value: '35' } });
    await waitFor(() => expect(screen.getByRole('button', { name: '应用边缘平滑' })).not.toBeDisabled());
    fireEvent.click(screen.getByRole('button', { name: '应用边缘平滑' }));

    await waitFor(() => expect(apiMock.smoothMaskGeometry).toHaveBeenCalledTimes(3));
    await waitFor(() => expect(useStore.getState().masks).toEqual([
      expect.objectContaining({ id: 'seed-mask', pathData: 'M 12 12 L 28 12 L 28 28 L 12 28 Z', saveStatus: 'dirty', saved: false }),
      expect.objectContaining({ id: 'prop-backward', pathData: 'M 12 12 L 28 12 L 28 28 L 12 28 Z', saveStatus: 'dirty', saved: false }),
      expect.objectContaining({ id: 'prop-forward', pathData: 'M 12 12 L 28 12 L 28 28 L 12 28 Z', saveStatus: 'dirty', saved: false }),
    ]));
    expect(useStore.getState().masks.every((mask) => !mask.metadata?.geometry_smoothing)).toBe(true);
    expect(screen.getByText('已应用边缘平滑强度 35，已同步应用到传播链 3 个对应 mask，强度已重置为 0，请保存后生效')).toBeInTheDocument();

    act(() => {
      useStore.getState().undoMasks();
    });
    expect(useStore.getState().masks.map((mask) => mask.pathData)).toEqual([
      'M 10 10 L 30 10 L 30 30 Z',
      'M 11 11 L 31 11 L 31 31 Z',
      'M 12 12 L 32 12 L 32 32 Z',
    ]);

    act(() => {
      useStore.getState().redoMasks();
    });
    expect(useStore.getState().masks.every((mask) => mask.pathData === 'M 12 12 L 28 12 L 28 28 L 12 28 Z')).toBe(true);
  });
});
