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

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

import VersionDrawer from '../VersionDrawer.vue';

const apiMocks = vi.hoisted(() => ({
  disableTemplate: vi.fn(),
  enableTemplate: vi.fn(),
  getTemplateDetail: vi.fn(),
  getTemplateHealth: vi.fn(),
  getTemplatePage: vi.fn(),
  publishTemplate: vi.fn(),
  switchCurrentTemplate: vi.fn(),
}));
const drawerState = vi.hoisted(() => ({ init: undefined as any }));
const healthOpenMock = vi.hoisted(() => vi.fn());
const stepSettingsOpenMock = vi.hoisted(() => vi.fn());
const drawerHookState = vi.hoisted(() => ({ calls: 0 }));
const localFormOpenMock = vi.hoisted(() => vi.fn());
const messageMocks = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn(), warning: vi.fn() }));
const modalState = vi.hoisted(() => ({ close: vi.fn(), open: vi.fn(), setProps: vi.fn() }));
const officeMock = vi.hoisted(() => vi.fn());
const tableState = vi.hoisted(() => ({
  actions: [] as any[],
  config: undefined as any,
  dropDownActions: [] as any[],
  primaryActions: [] as any[],
  reload: vi.fn(),
  row: {
    bizEnabled: 'enabled',
    bizStatus: 'PUBLISHED',
    id: 'template-v2',
    mubanBanben: 'R02',
    mubanBianhao: 'TMP-001',
    shifouDangqian: false,
  } as any,
}));

const TextareaStub = defineComponent({
  name: 'ATextarea',
  props: { value: String },
  emits: ['update:value'],
  setup(props, { emit }) {
    return () => h('textarea', { value: props.value, onInput: (event: Event) => emit('update:value', (event.target as HTMLTextAreaElement).value) });
  },
});
const FormItemStub = defineComponent({
  name: 'AFormItem',
  setup(_, { slots }) {
    return () => h('div', slots.default?.());
  },
});

vi.mock('#/api/x/eln/templates', () => ({
  disableTemplate: apiMocks.disableTemplate,
  enableTemplate: apiMocks.enableTemplate,
  getTemplateDetail: apiMocks.getTemplateDetail,
  getTemplateHealth: apiMocks.getTemplateHealth,
  getTemplatePage: apiMocks.getTemplatePage,
  publishTemplate: apiMocks.publishTemplate,
  switchCurrentTemplate: apiMocks.switchCurrentTemplate,
}));

vi.mock('#/utils/jnpf', () => ({ onlineUtils: { openOfficeDocument: officeMock } }));

vi.mock('#/components/GlobalFormModal/index.vue', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    default: defineComponent({
      name: 'GlobalFormModal',
      props: { listenGlobal: Boolean },
      setup(_, { expose }) {
        expose({ open: localFormOpenMock });
        return () => h('div', { 'data-test': 'drawer-local-form' });
      },
    }),
  };
});

vi.mock('../HealthDrawer.vue', async () => {
  const { defineComponent, h } = await import('vue');
  return { default: defineComponent({ name: 'ElnTemplateHealthDrawer', setup: () => () => h('div') }) };
});

vi.mock('../StepSettingsDrawer.vue', async () => {
  const { defineComponent, h } = await import('vue');
  return { default: defineComponent({ name: 'ElnTemplateStepSettingsDrawer', setup: () => () => h('div') }) };
});

vi.mock('../VersionModal.vue', async () => {
  const { defineComponent, h } = await import('vue');
  return { default: defineComponent({ name: 'ElnTemplateVersionModal', setup: () => () => h('div') }) };
});

vi.mock('@jnpf/hooks', () => ({
  useMessage: () => ({ createMessage: messageMocks }),
}));

vi.mock('@jnpf/ui/drawer', () => ({
  BasicDrawer: { name: 'BasicDrawer', template: '<div><slot /></div>' },
  useDrawer: () => [vi.fn(), { openDrawer: drawerHookState.calls++ % 2 === 0 ? healthOpenMock : stepSettingsOpenMock }],
  useDrawerInner: (init: any) => {
    drawerState.init = init;
    return [vi.fn()];
  },
}));

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', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicVxeTable: defineComponent({
      name: 'BasicVxeTable',
      setup(_, { slots }) {
        return () => h('div', slots.action?.({ record: tableState.row }));
      },
    }),
    TableAction: defineComponent({
      name: 'TableAction',
      props: { actions: Array, dropDownActions: Array },
      setup(props) {
        tableState.primaryActions = (props.actions || []) as any[];
        tableState.dropDownActions = (props.dropDownActions || []) as any[];
        tableState.actions = [...tableState.primaryActions, ...tableState.dropDownActions];
        return () => h('div');
      },
    }),
    useVxeTable: (config: any) => {
      tableState.config = config;
      return [vi.fn(), { reload: tableState.reload }];
    },
  };
});

describe('eLN template version drawer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    tableState.actions = [];
    tableState.dropDownActions = [];
    tableState.primaryActions = [];
    tableState.row = {
      bizEnabled: 'enabled',
      bizStatus: 'PUBLISHED',
      id: 'template-v2',
      mubanBanben: 'R02',
      mubanBianhao: 'TMP-001',
      shifouDangqian: false,
    };
    apiMocks.getTemplatePage.mockResolvedValue({
      data: { list: [tableState.row], pagination: { currentPage: 1, pageSize: 20, total: 1 } },
    });
    apiMocks.getTemplateDetail.mockResolvedValue({ data: tableState.row });
    apiMocks.getTemplateHealth.mockResolvedValue({ data: { diagnostics: [], fileId: 'file-1', templateId: 'template-v2' } });
    apiMocks.disableTemplate.mockResolvedValue({ data: undefined });
    apiMocks.enableTemplate.mockResolvedValue({ data: undefined });
    apiMocks.publishTemplate.mockResolvedValue({ data: undefined });
    apiMocks.switchCurrentTemplate.mockResolvedValue({ data: undefined });
  });

  it('按模板编号分页加载版本列表', async () => {
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    await drawerState.init({ mubanBianhao: 'TMP-001' });
    await tableState.config.api({ currentPage: 2, pageSize: 50 });

    expect(tableState.reload).toHaveBeenCalledWith({ page: 1, searchInfo: {} });
    expect(apiMocks.getTemplatePage).toHaveBeenCalledWith({ currentPage: 2, mubanBianhao: 'TMP-001', pageSize: 50, versionScope: 'ALL' });
  });

  it('版本列表显示创建时间', () => {
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    const creationTimeColumn = tableState.config.columns.find((column: any) => column.dataIndex === 'chuangjianShijian');
    expect(creationTimeColumn).toEqual(expect.objectContaining({ format: 'date|YYYY-MM-DD HH:mm:ss', title: '创建时间' }));
  });

  it('所有模板版本都可在抽屉内创建新版本', () => {
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    const createVersion = tableState.actions.find((action) => action.label === '创建新版本');
    expect(createVersion).toBeTruthy();

    createVersion.onClick();

    expect(modalState.open).toHaveBeenCalledWith(true, { sourceTemplateId: 'template-v2' });
  });

  it('行内保留高频操作并将生命周期操作收进更多菜单', () => {
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    expect(tableState.primaryActions.map((action) => action.label)).toEqual(['详情', '查看', '体检', '创建新版本']);
    expect(tableState.dropDownActions.map((action) => action.label)).toEqual(['步骤设置', '设为当前', '停用']);
  });

  it('允许将已发布且启用的非当前版本设为当前', async () => {
    const wrapper = mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': FormItemStub, 'a-tag': true, 'a-textarea': TextareaStub } },
    });

    const setCurrent = tableState.actions.find((action) => action.label === '设为当前');
    expect(setCurrent).toBeTruthy();
    setCurrent.onClick();
    wrapper.findComponent(TextareaStub).vm.$emit('update:value', '切换业务模板');
    await wrapper.vm.$nextTick();
    wrapper.findComponent({ name: 'BasicModal' }).vm.$emit('ok');
    await flushPromises();

    expect(apiMocks.switchCurrentTemplate).toHaveBeenCalledWith('TMP-001', 'template-v2', '切换业务模板');
    expect(messageMocks.success).toHaveBeenCalledWith('已将 R02 设为当前版本');
    expect(tableState.reload).toHaveBeenCalled();
  });

  it.each([
    ['enabled', '停用', apiMocks.disableTemplate, '暂时停用模板', '模板版本已停用'],
    ['disabled', '启用', apiMocks.enableTemplate, '恢复使用模板', '模板版本已启用'],
  ])('%s版本可在抽屉内执行%s操作', async (bizEnabled, actionLabel, request, operationReason, successMessage) => {
    tableState.row = { ...tableState.row, bizEnabled };
    const wrapper = mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': FormItemStub, 'a-tag': true, 'a-textarea': TextareaStub } },
    });

    tableState.actions.find((action) => action.label === actionLabel).onClick();
    wrapper.findComponent(TextareaStub).vm.$emit('update:value', operationReason);
    await wrapper.vm.$nextTick();
    wrapper.findComponent({ name: 'BasicModal' }).vm.$emit('ok');
    await flushPromises();

    expect(request).toHaveBeenCalledWith('template-v2', operationReason);
    expect(messageMocks.success).toHaveBeenCalledWith(successMessage);
    expect(tableState.reload).toHaveBeenCalled();
    expect(wrapper.emitted('reload')).toBeTruthy();
  });

  it('在版本抽屉内打开详情而不切换父页面', async () => {
    const wrapper = mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    await tableState.actions.find((action) => action.label === '详情').onClick();

    expect(apiMocks.getTemplateDetail).toHaveBeenCalledWith('template-v2');
    expect(localFormOpenMock).toHaveBeenCalledWith(expect.objectContaining({ id: 'template-v2', mode: 'detail', type: 'modal' }));
    expect(wrapper.find('[data-test="drawer-local-form"]').attributes('width')).toBe('1000px');
    expect(wrapper.emitted('detail')).toBeUndefined();
  });

  it('草稿版本在抽屉内完成标注和体检', async () => {
    tableState.row = { ...tableState.row, bizStatus: 'DRAFT', mubanFile: [{ fileExtension: 'docx', fileId: 'file-1', name: '原始模板.docx' }] };
    apiMocks.getTemplateDetail.mockResolvedValue({ data: tableState.row });
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    expect(tableState.actions.map((action) => action.label)).toEqual(expect.arrayContaining(['标注', '体检', '发布']));

    await tableState.actions.find((action) => action.label === '标注').onClick();
    expect(apiMocks.getTemplateDetail).toHaveBeenCalledWith('template-v2');
    expect(officeMock).toHaveBeenCalledWith(
      expect.objectContaining({
        bizDataId: 'template-v2',
        bizModule: 'eln.template',
        bizScene: 'eln.template.annotate',
        file: expect.objectContaining({ fileId: 'file-1' }),
        mode: 'edit',
      }),
    );

    await tableState.actions.find((action) => action.label === '体检').onClick();
    expect(healthOpenMock).toHaveBeenCalledWith(true, {
      bizStatus: 'DRAFT',
      file: expect.objectContaining({ fileId: 'file-1' }),
      templateCode: 'TMP-001',
      templateId: 'template-v2',
    });
  });

  it('草稿标注仅在手动保存后校验必备系统字段，关闭时不校验', async () => {
    tableState.row = { ...tableState.row, bizStatus: 'DRAFT', mubanFile: [{ fileExtension: 'docx', fileId: 'file-1', name: '原始模板.docx' }] };
    apiMocks.getTemplateDetail.mockResolvedValue({ data: tableState.row });
    apiMocks.getTemplateHealth.mockResolvedValue({
      data: {
        diagnostics: [
          { code: 'TEST_RESULT_BINDING_REQUIRED', message: '请在文档中标注“检验结果”', severity: 'ERROR' },
          { code: 'TEST_CONCLUSION_BINDING_REQUIRED', message: '请在文档中标注“检验结论”', severity: 'ERROR' },
          { code: 'MISSING_CONTROL', message: '绑定存在但 DOCX 控件已缺失', severity: 'ERROR' },
        ],
        fileId: 'file-1',
        templateId: 'template-v2',
      },
    });
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    await tableState.actions.find((action) => action.label === '标注').onClick();
    const officeConfig = officeMock.mock.calls[0]?.[0];

    expect(officeConfig.beforeClose).toBeUndefined();
    expect(officeConfig.onManualSave).toBeTypeOf('function');
    await expect(officeConfig.onManualSave()).rejects.toThrow('标注未完成：请在文档中标注“检验结果”；请在文档中标注“检验结论”');
    expect(apiMocks.getTemplateHealth).toHaveBeenCalledWith('template-v2');
  });

  it('所有版本都可从版本抽屉打开步骤设置', () => {
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    tableState.actions.find((action) => action.label === '步骤设置').onClick();

    expect(stepSettingsOpenMock).toHaveBeenCalledWith(true, { version: tableState.row });
  });

  it.each([
    ['停用草稿', { bizEnabled: 'disabled', bizStatus: 'DRAFT' }],
    ['已发布版本', { bizEnabled: 'enabled', bizStatus: 'PUBLISHED' }],
  ])('%s使用由后端剔除标注插件的只读查看入口', async (_label, state) => {
    tableState.row = {
      ...tableState.row,
      ...state,
      mubanFile: [{ fileExtension: 'docx', fileId: 'file-1', name: '原始模板.docx' }],
    };
    apiMocks.getTemplateDetail.mockResolvedValue({ data: tableState.row });
    mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': true, 'a-tag': true, 'a-textarea': true } },
    });

    expect(tableState.actions.map((action) => action.label)).toContain('查看');
    expect(tableState.actions.map((action) => action.label)).not.toContain('标注');

    await tableState.actions.find((action) => action.label === '查看').onClick();

    expect(officeMock).toHaveBeenCalledWith({
      bizDataId: 'template-v2',
      bizModule: 'eln.template',
      bizScene: 'eln.template.annotate',
      file: expect.objectContaining({ fileId: 'file-1' }),
      mode: 'view',
    });
  });

  it('草稿版本在抽屉内填写原因后发布', async () => {
    tableState.row = { ...tableState.row, bizStatus: 'DRAFT' };
    const wrapper = mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': FormItemStub, 'a-tag': true, 'a-textarea': TextareaStub } },
    });

    tableState.actions.find((action) => action.label === '发布').onClick();
    wrapper.findComponent(TextareaStub).vm.$emit('update:value', '版本验收完成');
    await wrapper.vm.$nextTick();
    wrapper.findComponent({ name: 'BasicModal' }).vm.$emit('ok');
    await flushPromises();

    expect(apiMocks.publishTemplate).toHaveBeenCalledWith('template-v2', '版本验收完成');
    expect(messageMocks.success).toHaveBeenCalledWith('模板版本已发布');
    expect(tableState.reload).toHaveBeenCalled();
    expect(wrapper.emitted('reload')).toBeTruthy();
  });

  it('发布请求期间显示确定按钮 loading，并在完成后关闭', async () => {
    let resolveRequest!: (value: unknown) => void;
    apiMocks.publishTemplate.mockImplementationOnce(
      () =>
        new Promise((resolve) => {
          resolveRequest = resolve;
        }),
    );
    tableState.row = { ...tableState.row, bizStatus: 'DRAFT' };
    const wrapper = mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': FormItemStub, 'a-tag': true, 'a-textarea': TextareaStub } },
    });

    tableState.actions.find((action) => action.label === '发布').onClick();
    wrapper.findComponent(TextareaStub).vm.$emit('update:value', '版本验收完成');
    await wrapper.vm.$nextTick();
    wrapper.findComponent({ name: 'BasicModal' }).vm.$emit('ok');
    await vi.waitFor(() => expect(apiMocks.publishTemplate).toHaveBeenCalledOnce());

    expect(modalState.setProps).toHaveBeenLastCalledWith({ confirmLoading: true });
    resolveRequest({ data: undefined });
    await vi.waitFor(() => expect(modalState.setProps).toHaveBeenLastCalledWith({ confirmLoading: false }));
  });

  it('发布失败后关闭确定按钮 loading 并保留弹窗', async () => {
    apiMocks.publishTemplate.mockRejectedValueOnce(new Error('发布失败'));
    tableState.row = { ...tableState.row, bizStatus: 'DRAFT' };
    const wrapper = mount(VersionDrawer, {
      global: { stubs: { 'a-alert': true, 'a-form-item': FormItemStub, 'a-tag': true, 'a-textarea': TextareaStub } },
    });

    tableState.actions.find((action) => action.label === '发布').onClick();
    wrapper.findComponent(TextareaStub).vm.$emit('update:value', '版本验收完成');
    await wrapper.vm.$nextTick();
    wrapper.findComponent({ name: 'BasicModal' }).vm.$emit('ok');
    await vi.waitFor(() => expect(messageMocks.error).toHaveBeenCalledWith('发布失败'));

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