import { mount, shallowMount } from '@vue/test-utils';
import { defineComponent, h } from 'vue';

import { beforeEach, describe, expect, it, vi } from 'vitest';

import HealthDrawer from '../HealthDrawer.vue';
import drawerSource from '../HealthDrawer.vue?raw';

const apiMocks = vi.hoisted(() => ({ confirmRemoveMissing: vi.fn(), getTemplateHealth: vi.fn() }));
const drawerState = vi.hoisted(() => {
  const state = {
    changeLoading: vi.fn((loading: boolean) => {
      state.loading = loading;
    }),
    init: undefined as any,
    loading: false,
  };
  return state;
});
const officeMock = vi.hoisted(() => vi.fn());
const modalState = vi.hoisted(() => ({ close: vi.fn(), open: vi.fn(), setProps: vi.fn() }));
const permissionState = vi.hoisted(() => ({ canEdit: true, requested: [] as string[] }));

const DiagnosticListStub = defineComponent({
  name: 'DiagnosticListStub',
  props: { dataSource: { default: () => [], type: Array } },
  setup(props, { slots }) {
    return () =>
      h(
        'div',
        props.dataSource.map((item) => slots.renderItem?.({ item })),
      );
  },
});
const DiagnosticListItemStub = defineComponent({
  name: 'DiagnosticListItemStub',
  setup(_, { slots }) {
    return () => h('div', [slots.default?.(), slots.actions?.()]);
  },
});
const DiagnosticListItemMetaStub = defineComponent({
  name: 'DiagnosticListItemMetaStub',
  props: { description: String },
  setup(props, { slots }) {
    return () =>
      h('div', [
        h('div', { 'data-test': 'diagnostic-title' }, slots.title?.()),
        props.description ? h('div', { 'data-test': 'diagnostic-description' }, props.description) : null,
      ]);
  },
});

vi.mock('#/api/x/eln/templates', () => ({
  confirmRemoveMissing: apiMocks.confirmRemoveMissing,
  getTemplateHealth: apiMocks.getTemplateHealth,
}));
vi.mock('#/utils/jnpf', () => ({ onlineUtils: { openOfficeDocument: officeMock } }));
vi.mock('@jnpf/hooks', () => ({
  useMessage: () => ({ createMessage: { success: vi.fn(), warning: vi.fn() } }),
  usePermission: () => ({
    hasBtnP: (code: string) => {
      permissionState.requested.push(code);
      return permissionState.canEdit && code === 'btn_edit';
    },
  }),
}));
vi.mock('@jnpf/ui/drawer', () => ({
  BasicDrawer: { name: 'BasicDrawer', props: { title: String }, template: '<div><slot /></div>' },
  useDrawerInner: (init: any) => {
    drawerState.init = init;
    return [vi.fn(), { changeLoading: drawerState.changeLoading }];
  },
}));
vi.mock('@jnpf/ui/modal', () => ({
  BasicModal: { name: 'BasicModal', template: '<div><slot /></div>' },
  useModal: () => [vi.fn(), { closeModal: modalState.close, openModal: modalState.open, setModalProps: modalState.setProps }],
}));
vi.mock('@jnpf/ui/vxeTable', () => ({ TableAction: { name: 'TableAction', template: '<div />' } }));

describe('eLN template health drawer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    drawerState.loading = false;
    permissionState.canEdit = true;
    permissionState.requested = [];
    apiMocks.getTemplateHealth.mockResolvedValue({
      data: {
        diagnostics: [
          { bindingId: 'b1', code: 'MISSING_CONTROL', expectedVersion: 3, message: '控件缺失', severity: 'ERROR' },
          { code: 'ORPHAN_CONTROL', controlTag: 'eln.field.orphan', message: '孤儿控件', severity: 'WARNING' },
          { code: 'UNSUPPORTED_REPEAT_CONTROL', message: '重复表格', severity: 'ERROR' },
        ],
        fileId: 'file-1',
        templateId: 'template-1',
      },
    });
  });

  it('在标题中显示模板编号', async () => {
    const wrapper = shallowMount(HealthDrawer);

    await drawerState.init({
      bizStatus: 'DRAFT',
      file: { fileId: 'file-1', name: '模板.docx' },
      templateCode: 'TMP-001',
      templateId: 'template-1',
    });

    expect(wrapper.findComponent({ name: 'BasicDrawer' }).props('title')).toBe('模板体检 - TMP-001');
  });

  it('诊断信息仅显示业务文案，并隐藏原始 code 和空辅助信息', async () => {
    apiMocks.getTemplateHealth.mockResolvedValueOnce({
      data: {
        diagnostics: [{ code: 'TEST_RESULT_BINDING_REQUIRED', message: '请在文档中标注“检验结果”', severity: 'ERROR' }],
        fileId: 'file-1',
        templateId: 'template-1',
      },
    });
    const wrapper = mount(HealthDrawer, {
      global: {
        stubs: {
          'a-alert': true,
          'a-button': true,
          'a-form-item': true,
          'a-tag': true,
          'a-textarea': true,
          Empty: true,
          AList: DiagnosticListStub,
          AListItem: DiagnosticListItemStub,
          AListItemMeta: DiagnosticListItemMetaStub,
        },
      },
    });

    await drawerState.init({
      bizStatus: 'DRAFT',
      file: { fileId: 'file-1', name: '模板.docx' },
      templateCode: 'TMP-001',
      templateId: 'template-1',
    });

    expect(wrapper.find('[data-test="diagnostic-title"]').text()).toBe('请在文档中标注“检验结果”');
    expect(wrapper.find('[data-test="diagnostic-description"]').exists()).toBe(false);
    expect(wrapper.text()).not.toContain('TEST_RESULT_BINDING_REQUIRED');
  });

  it('groups diagnostics by severity and keeps repair actions evidence-specific', async () => {
    const wrapper = shallowMount(HealthDrawer, {
      global: {
        stubs: {
          'a-alert': true,
          'a-button': true,
          'a-empty': true,
          'a-form-item': true,
          'a-list': true,
          'a-list-item': true,
          'a-list-item-meta': true,
          'a-tag': true,
          'a-textarea': true,
        },
      },
    });
    await drawerState.init({ file: { fileId: 'file-1', name: '原始模板.docx' }, templateId: 'template-1' });

    expect(apiMocks.getTemplateHealth).toHaveBeenCalledWith('template-1');
    expect(drawerSource).toContain('groupedDiagnostics');
    expect(drawerSource).toContain("action === 'repair'");
    expect(drawerSource).toContain("action === 'confirmRemove'");
    expect(drawerSource).toContain("action === 'locate'");
    expect(drawerSource).not.toContain("code === 'UNSUPPORTED_REPEAT_CONTROL' && action === 'repair'");
    expect(wrapper.findComponent({ name: 'BasicDrawer' }).exists()).toBe(true);
  });

  it('显式引入体检列表组件，避免真实页面把它们解析为未知元素', () => {
    expect(drawerSource).toContain("import { Empty, List, ListItem, ListItemMeta } from 'ant-design-vue';");
    expect(drawerSource).toContain('<Empty description="未发现模板绑定异常"');
    expect(drawerSource).toContain('<List :data-source=');
    expect(drawerSource).toContain('<ListItem>');
    expect(drawerSource).toContain('<ListItemMeta');
  });

  it('无异常时在抽屉内容区垂直居中显示空状态', async () => {
    apiMocks.getTemplateHealth.mockResolvedValueOnce({ data: { diagnostics: [], fileId: 'file-1', templateId: 'template-1' } });
    const wrapper = mount(HealthDrawer, {
      global: {
        stubs: {
          'a-alert': true,
          'a-button': true,
          'a-form-item': true,
          'a-tag': true,
          'a-textarea': true,
          Empty: true,
          List: true,
          ListItem: true,
          ListItemMeta: true,
        },
      },
    });

    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-1', name: '模板.docx' }, templateId: 'template-1' });

    expect(wrapper.find('.health-content').exists()).toBe(true);
    expect(wrapper.find('.health-empty').exists()).toBe(true);
    expect(drawerSource).toContain('class="full-drawer"');
    expect(drawerSource).toMatch(/\.health-content\s*\{[^}]*height:\s*100%/);
    expect(drawerSource).toMatch(/\.health-empty\s*\{[^}]*align-items:\s*center[^}]*justify-content:\s*center/);
  });

  it('模板切换后旧体检响应不得覆盖新模板报告或关闭其 loading', async () => {
    let resolveA!: (value: unknown) => void;
    let resolveB!: (value: unknown) => void;
    apiMocks.getTemplateHealth.mockImplementation(
      (templateId: string) =>
        new Promise((resolve) => {
          if (templateId === 'template-a') resolveA = resolve;
          else resolveB = resolve;
        }),
    );
    const wrapper = shallowMount(HealthDrawer);
    const loadA = drawerState.init({ file: { fileId: 'file-a', name: 'A.docx' }, templateId: 'template-a' });
    await vi.waitFor(() => expect(apiMocks.getTemplateHealth).toHaveBeenCalledWith('template-a'));
    const loadB = drawerState.init({ file: { fileId: 'file-b', name: 'B.docx' }, templateId: 'template-b' });
    await vi.waitFor(() => expect(apiMocks.getTemplateHealth).toHaveBeenCalledWith('template-b'));

    resolveB({ data: { diagnostics: [], fileId: 'file-b', templateId: 'template-b' } });
    await loadB;
    resolveA({ data: { diagnostics: [], fileId: 'file-a', templateId: 'template-a' } });
    await loadA;

    expect((wrapper.vm as any).report.templateId).toBe('template-b');
    expect(drawerState.loading).toBe(false);
    expect(drawerState.changeLoading).toHaveBeenCalledTimes(3);
  });

  it.each([
    ['定位', { actions: ['LOCATE'], code: 'ORPHAN_CONTROL', controlTag: 'eln.field.orphan' }],
    ['修复', { actions: ['REPAIR'], bindingId: 'binding-a', code: 'MISSING_CONTROL' }],
  ])('旧模板%s的保存回调不读取或干扰新模板体检', async (_label, diagnostic) => {
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-a', name: 'A.docx' }, templateId: 'template-a' });
    const [action] = (wrapper.vm as any).getDiagnosticActions(diagnostic);
    if (_label === '修复') expect(permissionState.requested).toEqual([]);
    action.onClick();
    const onSave = officeMock.mock.calls[0]?.[0].onSave as () => Promise<void>;
    let resolveB!: (value: unknown) => void;
    let healthRequestCount = 0;
    vi.clearAllMocks();
    drawerState.loading = false;
    apiMocks.getTemplateHealth.mockImplementation((templateId: string) => {
      expect(templateId).toBe('template-b');
      healthRequestCount += 1;
      if (healthRequestCount > 1) return Promise.resolve({ data: { diagnostics: [], fileId: 'file-b', templateId: 'template-b' } });
      return new Promise((resolve) => {
        resolveB = resolve;
      });
    });
    const loadB = drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-b', name: 'B.docx' }, templateId: 'template-b' });
    await vi.waitFor(() => expect(apiMocks.getTemplateHealth).toHaveBeenCalledOnce());

    await onSave();

    expect(apiMocks.getTemplateHealth).toHaveBeenCalledOnce();
    expect(drawerState.loading).toBe(true);
    resolveB({ data: { diagnostics: [], fileId: 'file-b', templateId: 'template-b' } });
    await loadB;
    expect(drawerState.loading).toBe(false);
  });

  it('当前体检请求失败时仍关闭 loading 并保留错误传播', async () => {
    apiMocks.getTemplateHealth.mockRejectedValueOnce(new Error('health failed'));
    const wrapper = shallowMount(HealthDrawer);

    await expect(drawerState.init({ file: { fileId: 'file-1', name: '模板.docx' }, templateId: 'template-1' })).rejects.toThrow('health failed');

    expect(wrapper.exists()).toBe(true);
    expect(drawerState.loading).toBe(false);
    expect(drawerState.changeLoading).toHaveBeenCalledWith(false);
  });

  it('opens repair in the same office modal with the binding id', () => {
    expect(drawerSource).toContain('repairBindingId: diagnostic.bindingId');
    expect(drawerSource).toContain("bizModule: 'eln.template'");
    expect(drawerSource).toContain("bizScene: 'eln.template.annotate'");
  });

  it('shows dependencies and requires a reason before confirming removal', () => {
    expect(drawerSource).toContain('引用/公式依赖');
    expect(drawerSource).toContain('pendingDiagnostic?.dependencies');
    expect(drawerSource).toContain('reason.value.trim()');
    expect(drawerSource).toContain('expectedVersion == null');
  });

  it('删除缺失绑定期间显示确定按钮 loading，并在完成后关闭', async () => {
    let resolveRequest!: (value: unknown) => void;
    apiMocks.confirmRemoveMissing.mockImplementationOnce(
      () =>
        new Promise((resolve) => {
          resolveRequest = resolve;
        }),
    );
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-1', name: '模板.docx' }, templateId: 'template-1' });
    (wrapper.vm as any).requestRemove({ bindingId: 'b1', dependencies: [], expectedVersion: 3 });
    (wrapper.vm as any).reason = '确认删除无效绑定';

    const submit = (wrapper.vm as any).confirmRemove();
    await vi.waitFor(() => expect(apiMocks.confirmRemoveMissing).toHaveBeenCalledOnce());

    expect(modalState.setProps).toHaveBeenLastCalledWith({ confirmLoading: true });
    resolveRequest({ data: undefined });
    await submit;
    expect(modalState.setProps).toHaveBeenLastCalledWith({ confirmLoading: false });
  });

  it('删除缺失绑定失败后关闭确定按钮 loading 并保留弹窗', async () => {
    apiMocks.confirmRemoveMissing.mockRejectedValueOnce(new Error('删除失败'));
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-1', name: '模板.docx' }, templateId: 'template-1' });
    (wrapper.vm as any).requestRemove({ bindingId: 'b1', dependencies: [], expectedVersion: 3 });
    (wrapper.vm as any).reason = '确认删除无效绑定';

    await expect((wrapper.vm as any).confirmRemove()).rejects.toThrow('删除失败');

    expect(modalState.close).not.toHaveBeenCalled();
    expect(modalState.setProps).toHaveBeenNthCalledWith(1, { confirmLoading: true });
    expect(modalState.setProps).toHaveBeenLastCalledWith({ confirmLoading: false });
  });

  it('草稿模板定位孤儿控件时使用只读模式和明确 Tag', async () => {
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-1', name: '原始模板.docx' }, templateId: 'template-1' });
    const [locate] = (wrapper.vm as any).getDiagnosticActions({ actions: ['LOCATE'], code: 'ORPHAN_CONTROL', controlTag: 'eln.field.orphan' });

    locate.onClick();

    expect(officeMock).toHaveBeenCalledWith(expect.objectContaining({ locateControlTag: 'eln.field.orphan', mode: 'view' }));
  });

  it('已发布模板体检隐藏修复和确认删除，只保留只读定位', async () => {
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'PUBLISHED', file: { fileId: 'file-1', name: '原始模板.docx' }, templateId: 'template-1' });

    expect((wrapper.vm as any).getDiagnosticActions({ actions: ['REPAIR', 'CONFIRM_REMOVE'], bindingId: 'b1', code: 'MISSING_CONTROL' })).toEqual([]);
    expect((wrapper.vm as any).getDiagnosticActions({ actions: ['LOCATE'], code: 'ROLE_MISMATCH', controlTag: 'eln.field.role-mismatch' })).toHaveLength(1);
  });

  it('仅显示后端授权且符合状态限制的诊断动作', async () => {
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-1', name: '原始模板.docx' }, templateId: 'template-1' });

    expect((wrapper.vm as any).getDiagnosticActions({ actions: ['LOCATE'], bindingId: 'b1', code: 'MISSING_CONTROL' })).toHaveLength(1);
    expect((wrapper.vm as any).getDiagnosticActions({ actions: [], bindingId: 'b1', code: 'MISSING_CONTROL' })).toEqual([]);
  });

  it('不因按钮权限隐藏草稿模板的修复和确认删除', async () => {
    permissionState.canEdit = false;
    const wrapper = shallowMount(HealthDrawer);
    await drawerState.init({ bizStatus: 'DRAFT', file: { fileId: 'file-1', name: '原始模板.docx' }, templateId: 'template-1' });

    expect((wrapper.vm as any).getDiagnosticActions({ actions: ['REPAIR', 'CONFIRM_REMOVE'], bindingId: 'b1', code: 'MISSING_CONTROL' })).toHaveLength(2);
    expect((wrapper.vm as any).getDiagnosticActions({ actions: ['LOCATE'], code: 'ROLE_MISMATCH', controlTag: 'eln.field.role-mismatch' })).toHaveLength(1);
  });
});
