import { WeixinAdapter } from '../../../../../src/modules/desktop_op/runtime/adapters/weixin_adapter.js';
import type { DesktopTask } from '../../../../../src/modules/desktop_op/runtime/types.js';

/** 构造最小合法 DesktopTask */
function makeTask(overrides: Partial<DesktopTask> = {}): DesktopTask {
  return {
    id: 'test-1',
    appId: 'weixin',
    target: { conversation: '测试群' },
    actionType: 'send-text',
    params: { text: '你好，世界' },
    enqueuedAt: Date.now(),
    ...overrides,
  };
}

/** mock ctx */
const makeCtx = () => ({
  window: {
    hwnd: 1,
    pid: 1,
    appName: 'Weixin',
    title: '微信',
    bounds: { x: 0, y: 0, width: 100, height: 100 },
    nsWindow: {},
  },
  screenshot: { captureWindow: jest.fn(() => Buffer.from('fake')) },
  vlm: { verifyState: jest.fn() },
  parser: {},
  logger: { info: jest.fn() },
  task: null as any,
  modelCalls: 0,
});

describe('WeixinAdapter', () => {
  let adapter: WeixinAdapter;

  beforeEach(() => {
    adapter = new WeixinAdapter();
    (adapter as any).windowLocator = { findByAppName: jest.fn() };
  });

  // ===== 用例 1：静态属性 =====
  it('appId === weixin，supportedActions === [send-text]', () => {
    expect(adapter.appId).toBe('weixin');
    expect(adapter.supportedActions).toEqual(['send-text']);
  });

  // ===== 用例 2：findWindow 正常返回句柄 =====
  it('findWindow — 定位器返回句柄时，适配器原样返回', async () => {
    const handle = {
      hwnd: 42,
      pid: 100,
      appName: 'Weixin',
      title: '微信',
      bounds: { x: 0, y: 0, width: 800, height: 600 },
      nsWindow: {},
    };
    (adapter as any).windowLocator.findByAppName.mockReturnValue(handle);

    const result = await adapter.findWindow({});

    expect((adapter as any).windowLocator.findByAppName).toHaveBeenCalledWith('Weixin', {
      skipMinimized: true,
      largest: true,
    });
    expect(result).toBe(handle);
  });

  // ===== 用例 3：findWindow 返回 null =====
  it('findWindow — 定位器返回 null 时适配器返回 null', async () => {
    (adapter as any).windowLocator.findByAppName.mockReturnValue(null);
    const result = await adapter.findWindow({});
    expect(result).toBeNull();
  });

  // ===== 用例 4：preFlightCheck 正常通过 =====
  it('preFlightCheck 正常 — VLM 返回 true 时不抛错', async () => {
    const ctx = makeCtx();
    ctx.vlm.verifyState.mockResolvedValue(true);
    const task = makeTask();

    await expect(adapter.preFlightCheck(task, ctx as any)).resolves.toBeUndefined();

    expect(ctx.screenshot.captureWindow).toHaveBeenCalledWith(ctx.window);
    expect(ctx.vlm.verifyState).toHaveBeenCalledTimes(1);
  });

  // ===== 用例 5：preFlightCheck — VLM 返回 false 抛错 =====
  it('preFlightCheck 失败 — VLM 返回 false 时抛 precondition-failed.*not in target chat', async () => {
    const ctx = makeCtx();
    ctx.vlm.verifyState.mockResolvedValue(false);
    const task = makeTask({ target: { conversation: '目标群' } });

    await expect(adapter.preFlightCheck(task, ctx as any)).rejects.toThrow(
      /precondition-failed.*not in target chat/,
    );
  });

  // ===== 用例 6：preFlightCheck — 缺少 conversation =====
  it('preFlightCheck — target.conversation 为空时抛 precondition-failed.*missing target.conversation', async () => {
    const ctx = makeCtx();
    const task = makeTask({ target: {} });

    await expect(adapter.preFlightCheck(task, ctx as any)).rejects.toThrow(
      /precondition-failed.*missing target.conversation/,
    );
  });

  // ===== 用例 7：buildSteps 返回精确 5 步 =====
  it('buildSteps 返回 5 步（顺序严格）', async () => {
    const task = makeTask({ params: { text: 'Hello' } });
    const steps = await adapter.buildSteps(task);

    expect(steps).toHaveLength(5);
    expect(steps[0]).toEqual({ type: 'clipboard-write', text: 'Hello' });
    expect(steps[1]).toEqual({ type: 'hotkey', key: 'ctrl+v' });
    expect(steps[2]).toEqual({ type: 'wait', ms: 400 });
    expect(steps[3]).toEqual({ type: 'hotkey', key: 'enter' });
    expect(steps[4]).toEqual({ type: 'wait', ms: 800 });
  });

  // ===== 用例 8：buildSteps text 为空抛错 =====
  it('buildSteps — text 为空时抛 invalid-params: text empty', async () => {
    const task = makeTask({ params: { text: '' } });

    await expect(adapter.buildSteps(task)).rejects.toThrow('invalid-params: text empty');
  });

  // ===== 用例 9：verifyResult 返回 true =====
  it('verifyResult — VLM 返回 true 时适配器返回 true，question 含目标文本前50字符', async () => {
    const ctx = makeCtx();
    ctx.vlm.verifyState.mockResolvedValue(true);
    const text = '这是一条测试消息';
    const task = makeTask({ params: { text } });

    const result = await adapter.verifyResult(task, ctx as any);

    expect(result).toBe(true);
    // 验证 question 包含文本截取
    const callArgs = ctx.vlm.verifyState.mock.calls[0];
    expect(callArgs[1]).toContain(text.slice(0, 50));
  });

  // ===== 用例 10：verifyResult 返回 false =====
  it('verifyResult — VLM 返回 false 时适配器返回 false', async () => {
    const ctx = makeCtx();
    ctx.vlm.verifyState.mockResolvedValue(false);
    const task = makeTask({ params: { text: '消息内容' } });

    const result = await adapter.verifyResult(task, ctx as any);

    expect(result).toBe(false);
  });

  // ===== 用例 11：queueKey 有 conversation =====
  it('queueKey({conversation: "文件传输助手"}) 返回 "文件传输助手"', () => {
    expect(adapter.queueKey({ conversation: '文件传输助手' })).toBe('文件传输助手');
  });

  // ===== 用例 12：queueKey 无 conversation =====
  it('queueKey({}) 返回 "default"', () => {
    expect(adapter.queueKey({})).toBe('default');
  });
});
