import type { Router } from 'vue-router';

/* eslint-disable vue/one-component-per-file -- Inline stubs isolate the detail workflow. */
import { flushPromises, mount } from '@vue/test-utils';
import { createMemoryHistory, createRouter } from 'vue-router';

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

import SkillDetail from './SkillDetail.vue';

const apiMocks = vi.hoisted(() => ({
  getSkillCategories: vi.fn(),
  getSkillDetail: vi.fn(),
  submitSkillDeleteApproval: vi.fn(),
  submitSkillOfflineApproval: vi.fn(),
  submitSkillPublishApproval: vi.fn(),
}));
const childMocks = vi.hoisted(() => ({
  getSkillContent: vi.fn<() => string | undefined>(() => undefined),
  save: vi.fn<() => Promise<boolean>>(async () => true),
  validate: vi.fn<() => Promise<boolean>>(async () => true),
}));

vi.mock('vue-router', async (importOriginal) => {
  const original = await importOriginal<typeof import('vue-router')>();
  return {
    ...original,
    onBeforeRouteLeave: vi.fn(),
    onBeforeRouteUpdate: vi.fn(),
  };
});
vi.mock('./api', () => apiMocks);
vi.mock('#/api/common/hasPermission', () => ({ hasPermission: () => true }));
vi.mock('./SkillResourceWorkbench.vue', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    default: defineComponent({
      name: 'SkillResourceWorkbench',
      props: { readonly: Boolean },
      emits: ['dirty', 'requestSave'],
      setup(props, { expose }) {
        expose({
          getSkillContent: childMocks.getSkillContent,
          saveAll: childMocks.save,
          validateAll: childMocks.validate,
        });
        return () =>
          h('div', {
            'data-readonly': String(props.readonly),
            'data-testid': 'resource-workbench',
          });
      },
    }),
  };
});
vi.mock('./SkillSettingsDialog.vue', async () => {
  const { defineComponent } = await import('vue');
  return {
    default: defineComponent({
      name: 'SkillSettingsDialog',
      setup: () => () => null,
    }),
  };
});

let router: Router;

async function mountDetail(id = '101') {
  router = createRouter({
    history: createMemoryHistory(),
    routes: [
      { component: { template: '<div />' }, path: '/ai/skill' },
      { component: { template: '<div />' }, path: '/ai/skill/detail/:id' },
    ],
  });
  await router.push(`/ai/skill/detail/${id}`);
  await router.isReady();
  const wrapper = mount(SkillDetail, {
    attachTo: document.body,
    global: {
      directives: { loading: {} },
      plugins: [router],
    },
  });
  await flushPromises();
  return wrapper;
}

function workbench(wrapper: Awaited<ReturnType<typeof mountDetail>>) {
  return wrapper.getComponent({ name: 'SkillResourceWorkbench' });
}

function publishButton(wrapper: Awaited<ReturnType<typeof mountDetail>>) {
  const button = wrapper
    .findAll('button')
    .find((candidate) => candidate.text().trim() === '发布');
  if (!button) throw new Error('未找到发布按钮');
  return button;
}

describe('skill detail runtime behavior', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    childMocks.getSkillContent.mockReturnValue(undefined);
    childMocks.save.mockResolvedValue(true);
    childMocks.validate.mockResolvedValue(true);
    apiMocks.getSkillCategories.mockResolvedValue({ data: [], errorCode: 0 });
    apiMocks.getSkillDetail.mockResolvedValue({
      data: {
        description: '用于验收统一工作台',
        displayName: '验收技能',
        displayPublishStatus: 'DRAFT',
        id: 101,
        manageable: true,
        name: 'acceptance-skill',
        publishStatus: 'DRAFT',
        skillContent:
          '---\nname: acceptance-skill\ndescription: 用于验收统一工作台\n---\n',
        visibilityScope: 'PRIVATE',
      },
      errorCode: 0,
    });
  });

  afterEach(() => {
    document.body.innerHTML = '';
    vi.restoreAllMocks();
  });

  it('returns the retired direct-new route to the Skill list', async () => {
    const wrapper = await mountDetail('new');

    expect(router.currentRoute.value.path).toBe('/ai/skill');
    expect(apiMocks.getSkillDetail).not.toHaveBeenCalled();
    wrapper.unmount();
  });

  it('stops publishing when saving dirty files fails', async () => {
    childMocks.save.mockResolvedValue(false);
    const wrapper = await mountDetail();
    workbench(wrapper).vm.$emit('dirty', true);
    await flushPromises();

    await publishButton(wrapper).trigger('click');
    await flushPromises();

    expect(childMocks.save).toHaveBeenCalledOnce();
    expect(childMocks.validate).not.toHaveBeenCalled();
    expect(apiMocks.submitSkillPublishApproval).not.toHaveBeenCalled();
    wrapper.unmount();
  });

  it('locks the editor while validating and stops on validation failure', async () => {
    let resolveValidation: ((result: boolean) => void) | undefined;
    childMocks.validate.mockImplementation(
      () =>
        new Promise<boolean>((resolve) => {
          resolveValidation = resolve;
        }),
    );
    const wrapper = await mountDetail();

    await publishButton(wrapper).trigger('click');
    await flushPromises();
    expect(workbench(wrapper).props('readonly')).toBe(true);

    resolveValidation?.(false);
    await flushPromises();
    expect(workbench(wrapper).props('readonly')).toBe(false);
    expect(apiMocks.submitSkillPublishApproval).not.toHaveBeenCalled();
    wrapper.unmount();
  });

  it('handles the save shortcut through the single workbench', async () => {
    const wrapper = await mountDetail();
    workbench(wrapper).vm.$emit('dirty', true);
    await flushPromises();
    const shortcut = new KeyboardEvent('keydown', {
      bubbles: true,
      cancelable: true,
      key: 's',
      metaKey: true,
    });

    window.dispatchEvent(shortcut);
    await flushPromises();

    expect(shortcut.defaultPrevented).toBe(true);
    expect(childMocks.save).toHaveBeenCalledOnce();
    wrapper.unmount();
  });
});
