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

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

import StepSettingsDrawer from '../StepSettingsDrawer.vue';

const apiMocks = vi.hoisted(() => ({
  getTemplateSteps: vi.fn(),
  saveTemplateSteps: vi.fn(),
}));
const drawerState = vi.hoisted(() => ({ init: undefined as any }));
const drawerActions = vi.hoisted(() => ({
  changeLoading: vi.fn(),
  changeOkLoading: vi.fn(),
  close: vi.fn(),
}));
const messageMocks = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn(), warning: vi.fn() }));
const modalActions = vi.hoisted(() => ({ close: vi.fn(), open: vi.fn() }));

const configuration = {
  singleStep: false,
  steps: [
    { id: 'step-2', name: '记录结果', order: 2 },
    { id: 'step-1', name: '准备样品', order: 1 },
  ],
};

const InputStub = defineComponent({
  name: 'AInput',
  inheritAttrs: false,
  props: { value: String },
  emits: ['update:value'],
  setup(props, { attrs, emit }) {
    return () =>
      h('input', {
        ...attrs,
        value: props.value,
        onInput: (event: Event) => emit('update:value', (event.target as HTMLInputElement).value),
      });
  },
});

const SelectStub = defineComponent({
  name: 'ASelect',
  inheritAttrs: false,
  props: { options: { type: Array, default: () => [] }, value: String },
  emits: ['update:value'],
  setup(props, { attrs, emit }) {
    return () =>
      h(
        'select',
        {
          ...attrs,
          value: props.value,
          onChange: (event: Event) => emit('update:value', (event.target as HTMLSelectElement).value || undefined),
        },
        [h('option', { value: '' }), ...props.options.map((item: any) => h('option', { value: item.value }, item.label))],
      );
  },
});

const ButtonStub = defineComponent({
  name: 'AButton',
  inheritAttrs: false,
  props: { disabled: Boolean },
  emits: ['click'],
  setup(props, { attrs, emit, slots }) {
    return () => h('button', { ...attrs, disabled: props.disabled, onClick: () => emit('click') }, [slots.icon?.(), slots.default?.()]);
  },
});

vi.mock('#/api/x/eln/templates', () => ({
  getTemplateSteps: apiMocks.getTemplateSteps,
  saveTemplateSteps: apiMocks.saveTemplateSteps,
}));

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

vi.mock('@jnpf/ui/drawer', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicDrawer: defineComponent({
      name: 'BasicDrawer',
      inheritAttrs: false,
      props: { showFooter: Boolean, title: String },
      emits: ['ok', 'register'],
      setup(_, { slots }) {
        return () => h('div', { 'data-test': 'basic-drawer' }, slots.default?.());
      },
    }),
    useDrawerInner: (init: any) => {
      drawerState.init = init;
      return [
        vi.fn(),
        {
          changeLoading: drawerActions.changeLoading,
          changeOkLoading: drawerActions.changeOkLoading,
          closeDrawer: drawerActions.close,
        },
      ];
    },
  };
});

vi.mock('@jnpf/ui/modal', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicModal: defineComponent({
      name: 'BasicModal',
      emits: ['ok', 'register'],
      setup(_, { slots }) {
        return () => h('div', { 'data-test': 'basic-modal' }, slots.default?.());
      },
    }),
    useModal: () => [vi.fn(), { closeModal: modalActions.close, openModal: modalActions.open }],
  };
});

vi.mock('vuedraggable', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    default: defineComponent({
      name: 'DraggableStub',
      inheritAttrs: false,
      props: { disabled: Boolean, modelValue: { type: Array, default: () => [] } },
      emits: ['update:modelValue'],
      setup(props, { attrs, slots }) {
        return () =>
          h(
            'div',
            { ...attrs, 'data-disabled': String(props.disabled) },
            props.modelValue.map((element, index) => slots.item?.({ element, index })),
          );
      },
    }),
  };
});

function mountDrawer() {
  return mount(StepSettingsDrawer, {
    global: {
      stubs: {
        'a-button': ButtonStub,
        'a-form-item': defineComponent({ name: 'AFormItem', template: '<div><slot /></div>' }),
        'a-input': InputStub,
        'a-select': SelectStub,
        'a-tooltip': defineComponent({ name: 'ATooltip', template: '<span><slot /></span>' }),
      },
    },
  });
}

describe('eLN template step settings drawer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    apiMocks.getTemplateSteps.mockResolvedValue({ data: structuredClone(configuration) });
    apiMocks.saveTemplateSteps.mockResolvedValue({ data: structuredClone(configuration) });
  });

  it('标题展示模板编号和模板版本', async () => {
    const wrapper = mountDrawer();

    await drawerState.init({
      version: { bizEnabled: 'enabled', bizStatus: 'DRAFT', id: 'template-1', mubanBanben: 'R01', mubanBianhao: 'TMP-001' },
    });
    await wrapper.vm.$nextTick();

    expect(wrapper.findComponent({ name: 'BasicDrawer' }).props('title')).toBe('步骤设置 - TMP-001 - R01');
  });

  it('加载已发布版本时按顺序只读展示', async () => {
    const wrapper = mountDrawer();

    await drawerState.init({
      version: { bizEnabled: 'enabled', bizStatus: 'PUBLISHED', id: 'template-1', mubanBanben: 'R01', mubanBianhao: 'TMP-001' },
    });
    await wrapper.vm.$nextTick();

    expect(apiMocks.getTemplateSteps).toHaveBeenCalledWith('template-1');
    expect(drawerActions.changeLoading.mock.calls).toEqual([[true], [false]]);
    expect(wrapper.findComponent({ name: 'BasicDrawer' }).props('showFooter')).toBe(false);
    expect(wrapper.findComponent('[data-test="step-list"]').attributes('data-disabled')).toBe('true');
    expect(wrapper.find('[data-test="step-list"]').text().indexOf('准备样品')).toBeLessThan(wrapper.find('[data-test="step-list"]').text().indexOf('记录结果'));
  });

  it('按拖动顺序保存步骤', async () => {
    const wrapper = mountDrawer();
    await drawerState.init({
      version: { bizEnabled: 'enabled', bizStatus: 'DRAFT', id: 'template-1', mubanBanben: 'R01', mubanBianhao: 'TMP-001' },
    });
    await wrapper.vm.$nextTick();

    const stepList = wrapper.findComponent({ name: 'DraggableStub' });
    stepList.vm.$emit('update:modelValue', [...(stepList.props('modelValue') as any[])].reverse());
    await wrapper.vm.$nextTick();

    wrapper.findComponent({ name: 'BasicDrawer' }).vm.$emit('ok');
    await flushPromises();

    expect(apiMocks.saveTemplateSteps).toHaveBeenCalledWith('template-1', {
      stepMigrations: {},
      steps: [
        { id: 'step-2', name: '记录结果' },
        { id: 'step-1', name: '准备样品' },
      ],
    });
    expect(drawerActions.changeOkLoading.mock.calls).toEqual([[true], [false]]);
    expect(drawerActions.close).toHaveBeenCalledOnce();
    expect(wrapper.emitted('saved')).toEqual([['template-1']]);
  });

  it('删除已有步骤时记录迁移目标', async () => {
    const wrapper = mountDrawer();
    await drawerState.init({
      version: { bizEnabled: 'enabled', bizStatus: 'DRAFT', id: 'template-1', mubanBanben: 'R01', mubanBianhao: 'TMP-001' },
    });
    await wrapper.vm.$nextTick();

    await wrapper.find('[data-test="add-step"]').trigger('click');
    await wrapper.find('[data-test="delete-step-step-1"]').trigger('click');
    expect(modalActions.open).toHaveBeenCalledWith(true);
    const migrationTarget = wrapper.findAllComponents(SelectStub).find((item) => item.attributes('data-test') === 'migration-target')!;
    expect(migrationTarget.props('options')).toEqual([{ label: '记录结果', value: 'step-2' }]);
    migrationTarget.vm.$emit('update:value', 'step-2');
    await wrapper.vm.$nextTick();
    wrapper.findComponent({ name: 'BasicModal' }).vm.$emit('ok');
    await wrapper.vm.$nextTick();
    await wrapper.find('[data-test="delete-step-local-step-1"]').trigger('click');
    wrapper.findComponent({ name: 'BasicDrawer' }).vm.$emit('ok');
    await flushPromises();

    expect(apiMocks.saveTemplateSteps).toHaveBeenCalledWith(
      'template-1',
      expect.objectContaining({
        stepMigrations: { 'step-1': 'step-2' },
        steps: [{ id: 'step-2', name: '记录结果' }],
      }),
    );
  });

  it('保存失败后恢复按钮 loading 并保持抽屉打开', async () => {
    apiMocks.saveTemplateSteps.mockRejectedValueOnce(new Error('保存失败'));
    const wrapper = mountDrawer();
    await drawerState.init({
      version: { bizEnabled: 'enabled', bizStatus: 'DRAFT', id: 'template-1', mubanBanben: 'R01', mubanBianhao: 'TMP-001' },
    });
    await wrapper.vm.$nextTick();

    wrapper.findComponent({ name: 'BasicDrawer' }).vm.$emit('ok');
    await flushPromises();

    expect(messageMocks.error).toHaveBeenCalledWith('保存失败');
    expect(drawerActions.changeOkLoading.mock.calls).toEqual([[true], [false]]);
    expect(drawerActions.close).not.toHaveBeenCalled();
  });
});
