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

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

import SkillCapabilityPanel from './SkillCapabilityPanel.vue';

const apiMocks = vi.hoisted(() => ({
  getSkillCapabilityBindings: vi.fn(),
  getSkillCapabilityCandidates: vi.fn(),
  getSkillCapabilityTools: vi.fn(),
  getSkillDetail: vi.fn(),
  replaceSkillCapabilityBindings: vi.fn(),
}));

vi.mock('./api', () => apiMocks);

let wrapper: ReturnType<typeof mount> | undefined;
const modalStub = {
  name: 'EasyFlowFormModal',
  props: ['open'],
  template:
    '<div v-if="open" data-testid="capability-dialog"><slot></slot><button data-testid="modal-confirm" @click="$emit(\'confirm\')">保存</button></div>',
};

function mountPanel() {
  wrapper = mount(SkillCapabilityPanel, {
    attachTo: document.body,
    global: {
      directives: { access: {}, loading: {} },
      stubs: { EasyFlowFormModal: modalStub },
    },
    props: {
      capabilityHash: 'a'.repeat(64),
      skillId: 101,
    },
  });
  return wrapper;
}

function mountEmbeddedPanel() {
  wrapper = mount(SkillCapabilityPanel, {
    attachTo: document.body,
    global: {
      directives: { access: {}, loading: {} },
      stubs: { EasyFlowFormModal: modalStub },
    },
    props: {
      capabilityHash: 'a'.repeat(64),
      embedded: true,
      skillId: 101,
    },
  });
  return wrapper;
}

describe('skill capability panel load isolation', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    apiMocks.getSkillCapabilityBindings.mockImplementation(
      (skillId: number | string) =>
        Promise.resolve(
          String(skillId) === '101'
            ? {
                data: [
                  {
                    capabilityType: 'WORKFLOW',
                    enabled: true,
                    runtimeName: 'old_workflow',
                    targetId: 11,
                    targetName: '旧 Skill 工作流',
                    targetStatus: 'AVAILABLE',
                  },
                ],
                errorCode: 0,
              }
            : {
                data: undefined,
                errorCode: 1,
                message: 'load failed',
              },
        ),
    );
    apiMocks.getSkillCapabilityCandidates.mockResolvedValue({
      data: [],
      errorCode: 0,
    });
    apiMocks.getSkillCapabilityTools.mockResolvedValue({
      data: { status: 'AVAILABLE', targetId: 11, toolNames: [] },
      errorCode: 0,
    });
    apiMocks.getSkillDetail.mockResolvedValue({
      data: { capabilityHash: 'b'.repeat(64) },
      errorCode: 0,
    });
    apiMocks.replaceSkillCapabilityBindings.mockResolvedValue({
      data: { bindings: [], capabilityHash: 'c'.repeat(64) },
      errorCode: 0,
    });
  });

  afterEach(() => {
    wrapper?.unmount();
    wrapper = undefined;
    document.body.innerHTML = '';
  });

  it('clears the previous Skill bindings and disables mutations when the next load fails', async () => {
    wrapper = mount(SkillCapabilityPanel, {
      attachTo: document.body,
      global: {
        directives: { access: {}, loading: {} },
        stubs: {
          EasyFlowFormModal: modalStub,
        },
      },
      props: {
        capabilityHash: 'a'.repeat(64),
        skillId: 101,
      },
    });
    await flushPromises();

    expect(wrapper.get('.skill-capability-panel__title').text()).toContain('1');
    expect(wrapper.find('.skill-capability-panel__list').exists()).toBe(true);

    const addButton = wrapper
      .findAll('button')
      .find((button) => button.text().includes('添加能力'));
    expect(addButton).toBeDefined();
    await addButton?.trigger('click');
    await flushPromises();
    expect(
      wrapper.findComponent({ name: 'EasyFlowFormModal' }).props('open'),
    ).toBe(true);

    await wrapper.setProps({
      capabilityHash: 'b'.repeat(64),
      skillId: 202,
    });
    await flushPromises();

    expect(apiMocks.getSkillCapabilityBindings).toHaveBeenNthCalledWith(1, 101);
    expect(apiMocks.getSkillCapabilityBindings).toHaveBeenNthCalledWith(2, 202);
    expect(wrapper.get('.skill-capability-panel__title').text()).toContain('0');
    expect(wrapper.find('.skill-capability-panel__list').exists()).toBe(false);
    expect(wrapper.text()).toContain('load failed');
    expect(
      wrapper.findComponent({ name: 'EasyFlowFormModal' }).props('open'),
    ).toBe(false);

    expect(wrapper.text()).not.toContain('保存绑定');
    expect(wrapper.text()).not.toContain('校验');
    const addButtons = wrapper
      .findAll('button')
      .filter((button) => button.text().includes('添加'));

    expect(addButtons.length).toBeGreaterThan(0);
    expect(
      addButtons.every((button) => button.attributes('disabled') !== undefined),
    ).toBe(true);
  });

  it('removes duplicated embedded labels and keeps only actionable save states', async () => {
    mountEmbeddedPanel();
    await flushPromises();

    expect(wrapper?.find('.skill-capability-panel__title').exists()).toBe(
      false,
    );
    expect(wrapper?.text()).not.toContain('已保存');

    apiMocks.replaceSkillCapabilityBindings.mockImplementation(
      () => new Promise(() => undefined),
    );
    wrapper?.findComponent({ name: 'ElSwitch' }).vm.$emit('change', false);
    await Promise.resolve();

    expect(wrapper?.text()).toContain('保存中');
  });

  it('loads MCP tools only for a selected scope and keeps failures retryable', async () => {
    apiMocks.getSkillCapabilityCandidates.mockResolvedValue({
      data: [
        {
          capabilityType: 'MCP',
          name: '知识库 MCP',
          status: 'AVAILABLE',
          targetId: 88,
        },
      ],
      errorCode: 0,
    });
    apiMocks.getSkillCapabilityTools
      .mockResolvedValueOnce({
        data: undefined,
        errorCode: 1,
        message: 'load failed',
      })
      .mockResolvedValueOnce({
        data: {
          status: 'AVAILABLE',
          targetId: 88,
          toolNames: ['search'],
        },
        errorCode: 0,
      });
    wrapper = mount(SkillCapabilityPanel, {
      attachTo: document.body,
      global: {
        directives: { access: {}, loading: {} },
        stubs: {
          EasyFlowFormModal: modalStub,
        },
      },
      props: {
        capabilityHash: 'a'.repeat(64),
        skillId: 101,
      },
    });
    await flushPromises();

    const addButton = wrapper
      .findAll('button')
      .find((button) => button.text().includes('添加能力'));
    expect(addButton).toBeDefined();
    await addButton?.trigger('click');
    await flushPromises();

    const typeSegmented = wrapper.findComponent({ name: 'ElSegmented' });
    expect(typeSegmented.exists()).toBe(true);
    typeSegmented.vm.$emit('update:modelValue', 'MCP');
    await flushPromises();

    let selects = wrapper.findAllComponents({ name: 'ElSelect' });
    const candidateSelect = selects.find((select) => select.props('remote'));
    expect(candidateSelect).toBeDefined();
    candidateSelect?.vm.$emit('update:modelValue', 88);
    await flushPromises();

    expect(apiMocks.getSkillCapabilityTools).not.toHaveBeenCalled();

    selects = wrapper.findAllComponents({ name: 'ElSelect' });
    const scopeSelect = selects.find(
      (select) => select.props('modelValue') === 'ALL',
    );
    expect(scopeSelect).toBeDefined();
    scopeSelect?.vm.$emit('update:modelValue', 'SELECTED');
    scopeSelect?.vm.$emit('change', 'SELECTED');
    await flushPromises();

    expect(apiMocks.getSkillCapabilityTools).toHaveBeenCalledTimes(1);
    expect(apiMocks.getSkillCapabilityTools).toHaveBeenLastCalledWith(88);
    expect(
      wrapper
        .findAllComponents({ name: 'ElFormItem' })
        .some((item) => item.props('error') === 'load failed'),
    ).toBe(true);

    const retryButton = wrapper
      .findAll('button')
      .find((button) => button.text().trim() === '重新加载');
    await retryButton?.trigger('click');
    await flushPromises();

    expect(apiMocks.getSkillCapabilityTools).toHaveBeenCalledTimes(2);
    expect(
      wrapper
        .findAllComponents({ name: 'ElFormItem' })
        .some((item) => item.props('error') === 'load failed'),
    ).toBe(false);
    expect(
      wrapper
        .findAllComponents({ name: 'ElOption' })
        .some((option) => option.props('value') === 'search'),
    ).toBe(true);
  });

  it('generates a stable runtime name for resources with a Chinese name', async () => {
    apiMocks.getSkillCapabilityCandidates.mockResolvedValue({
      data: [
        {
          capabilityType: 'WORKFLOW',
          name: '测试工作流',
          status: 'AVAILABLE',
          targetId: 88,
        },
      ],
      errorCode: 0,
    });
    mountPanel();
    await flushPromises();

    const addButton = wrapper
      ?.findAll('button')
      .find((button) => button.text().includes('添加能力'));
    await addButton?.trigger('click');
    await flushPromises();

    const candidateSelect = wrapper
      ?.findAllComponents({ name: 'ElSelect' })
      .find((select) => select.props('remote'));
    candidateSelect?.vm.$emit('update:modelValue', 88);
    await flushPromises();

    expect(
      wrapper?.findComponent({ name: 'ElInput' }).props('modelValue'),
    ).toBe('workflow_88');
  });

  it('automatically saves toggles with the expected hash and updates the quiet state', async () => {
    apiMocks.replaceSkillCapabilityBindings.mockImplementation(
      (_skillId: number | string, nextBindings: unknown[]) =>
        Promise.resolve({
          data: {
            bindings: structuredClone(nextBindings),
            capabilityHash: 'c'.repeat(64),
          },
          errorCode: 0,
        }),
    );
    mountPanel();
    await flushPromises();

    wrapper?.findComponent({ name: 'ElSwitch' }).vm.$emit('change', false);
    await flushPromises();

    expect(apiMocks.replaceSkillCapabilityBindings).toHaveBeenCalledWith(
      101,
      [expect.objectContaining({ enabled: false })],
      'a'.repeat(64),
    );
    expect(wrapper?.text()).toContain('已保存');
    expect(
      (wrapper?.vm as unknown as { hasDirty: () => boolean }).hasDirty(),
    ).toBe(false);
  });

  it('serializes rapid changes and persists the latest desired state', async () => {
    let resolveFirst:
      | ((value: {
          data: { bindings: unknown[]; capabilityHash: string };
          errorCode: number;
        }) => void)
      | undefined;
    apiMocks.replaceSkillCapabilityBindings
      .mockImplementationOnce(
        (_skillId: number | string, _nextBindings: unknown[]) =>
          new Promise((resolve) => {
            resolveFirst = resolve;
          }),
      )
      .mockImplementationOnce(
        (_skillId: number | string, nextBindings: unknown[]) =>
          Promise.resolve({
            data: {
              bindings: structuredClone(nextBindings),
              capabilityHash: 'd'.repeat(64),
            },
            errorCode: 0,
          }),
      );
    mountPanel();
    await flushPromises();

    const toggle = wrapper?.findComponent({ name: 'ElSwitch' });
    toggle?.vm.$emit('change', false);
    await Promise.resolve();
    toggle?.vm.$emit('change', true);
    resolveFirst?.({
      data: {
        bindings: [
          {
            capabilityType: 'WORKFLOW',
            enabled: false,
            runtimeName: 'old_workflow',
            targetId: 11,
            targetName: '旧 Skill 工作流',
            targetStatus: 'AVAILABLE',
          },
        ],
        capabilityHash: 'c'.repeat(64),
      },
      errorCode: 0,
    });
    await flushPromises();

    expect(apiMocks.replaceSkillCapabilityBindings).toHaveBeenCalledTimes(2);
    expect(apiMocks.replaceSkillCapabilityBindings).toHaveBeenNthCalledWith(
      2,
      101,
      [expect.objectContaining({ enabled: true })],
      'c'.repeat(64),
    );
    expect(wrapper?.text()).toContain('已保存');
  });

  it('rolls back an optimistic toggle when automatic save fails', async () => {
    apiMocks.replaceSkillCapabilityBindings.mockRejectedValue(
      new Error('服务暂时不可用'),
    );
    mountPanel();
    await flushPromises();

    const toggle = wrapper?.findComponent({ name: 'ElSwitch' });
    toggle?.vm.$emit('change', false);
    await flushPromises();

    expect(
      wrapper?.findComponent({ name: 'ElSwitch' }).props('modelValue'),
    ).toBe(true);
    expect(wrapper?.text()).toContain('服务暂时不可用');
    expect(wrapper?.text()).toContain('保存失败');
  });

  it('keeps local changes on conflict and can reapply them with a refreshed hash', async () => {
    apiMocks.replaceSkillCapabilityBindings
      .mockRejectedValueOnce({
        response: {
          data: { message: '能力配置已被其他操作更新' },
          status: 409,
        },
      })
      .mockImplementationOnce(
        (_skillId: number | string, nextBindings: unknown[]) =>
          Promise.resolve({
            data: {
              bindings: structuredClone(nextBindings),
              capabilityHash: 'd'.repeat(64),
            },
            errorCode: 0,
          }),
      );
    mountPanel();
    await flushPromises();

    const toggle = wrapper?.findComponent({ name: 'ElSwitch' });
    toggle?.vm.$emit('change', false);
    await flushPromises();

    expect(toggle?.props('modelValue')).toBe(false);
    expect(wrapper?.text()).toContain('本地修改已保留');

    const reapply = wrapper
      ?.findAll('button')
      .find((button) => button.text().trim() === '重新应用');
    await reapply?.trigger('click');
    await flushPromises();

    expect(apiMocks.replaceSkillCapabilityBindings).toHaveBeenNthCalledWith(
      2,
      101,
      [expect.objectContaining({ enabled: false })],
      'b'.repeat(64),
    );
    expect(wrapper?.text()).toContain('已保存');
  });
});
