/**
 * DesktopOpRuntime 单元测试
 * TDD 严格模式：先写测试，后写实现
 */

import { DesktopOpRuntime } from '../../../../src/modules/desktop_op/runtime/runtime.js';

// ── 测试辅助工厂函数 ──────────────────────────────────────────────────

/** 构造带默认 mock 的 runtime 实例（手动注入，绕过 IoC） */
function makeRuntime(overrides: Partial<any> = {}): DesktopOpRuntime {
  const r = new DesktopOpRuntime();
  (r as any).safetyGuard = overrides.safetyGuard ?? {
    validateTaskShape: jest.fn(),
    validateAppId: jest.fn(),
    validateAction: jest.fn(),
  };
  (r as any).adapterRegistry = overrides.adapterRegistry ?? { get: jest.fn() };
  (r as any).windowLocator = overrides.windowLocator ?? {
    activate: jest.fn(async () => {}),
  };
  (r as any).screenshooter = overrides.screenshooter ?? {};
  (r as any).input = overrides.input ?? {};
  (r as any).vlm = overrides.vlm ?? {};
  (r as any).actionExecutor = overrides.actionExecutor ?? {
    setInput: jest.fn(),
    execute: jest.fn(async () => {}),
  };
  (r as any).logger = overrides.logger ?? {
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  };
  return r;
}

/** 构造默认的 mock adapter */
const makeAdapter = (over: Partial<any> = {}) => ({
  appId: 'weixin',
  supportedActions: ['send-text'],
  findWindow: jest.fn().mockResolvedValue({
    hwnd: 1,
    pid: 1,
    appName: 'Weixin',
    title: '',
    bounds: { x: 0, y: 0, width: 100, height: 100 },
    nsWindow: {},
  }),
  preFlightCheck: jest.fn().mockResolvedValue(undefined),
  buildSteps: jest.fn().mockResolvedValue([
    { type: 'clipboard-write', text: 'hi' },
    { type: 'hotkey', key: 'enter' },
  ]),
  verifyResult: jest.fn().mockResolvedValue(true),
  queueKey: jest.fn().mockReturnValue('k'),
  ...over,
});

/** 构造合法 DesktopTask */
const task = (over: Partial<any> = {}) => ({
  id: 't1',
  appId: 'weixin',
  target: { conversation: 'X' },
  actionType: 'send-text',
  params: { text: 'hi' },
  enqueuedAt: Date.now(),
  ...over,
});

// ── 测试套件 ─────────────────────────────────────────────────────────

describe('DesktopOpRuntime', () => {
  beforeEach(() => {
    // 每个测试用例使用真实计时器（延迟很短，不阻塞）
    jest.useRealTimers();
  });

  // 测试 1: 正常路径 —— 所有校验通过，adapter 链返回 ok
  it('case 1: 正常路径 — result.ok===true，steps===2，modelCalls===0', async () => {
    const adapter = makeAdapter();
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    const result = await r.runTask(task(), new AbortController().signal);

    expect(result.ok).toBe(true);
    expect(result.steps).toBe(2);
    expect(result.modelCalls).toBe(0);

    // validateAction 对每个 step 调用一次
    const sg = (r as any).safetyGuard;
    expect(sg.validateAction).toHaveBeenCalledTimes(2);

    // actionExecutor.execute 调用两次
    const ae = (r as any).actionExecutor;
    expect(ae.execute).toHaveBeenCalledTimes(2);
  });

  // 测试 2: validateAppId 抛出 'app-not-allowed' → 透传
  it('case 2: validateAppId 抛出 app-not-allowed → runtime 原样重抛', async () => {
    const r = makeRuntime({
      safetyGuard: {
        validateTaskShape: jest.fn(),
        validateAppId: jest.fn().mockImplementation(() => {
          throw new Error('app-not-allowed');
        }),
        validateAction: jest.fn(),
      },
    });

    await expect(r.runTask(task(), new AbortController().signal)).rejects.toThrow(
      'app-not-allowed',
    );
  });

  // 测试 3: validateTaskShape 抛出 'invalid-task: id missing' → 透传
  it('case 3: validateTaskShape 抛出 invalid-task: id missing → runtime 原样重抛', async () => {
    const r = makeRuntime({
      safetyGuard: {
        validateTaskShape: jest.fn().mockImplementation(() => {
          throw new Error('invalid-task: id missing');
        }),
        validateAppId: jest.fn(),
        validateAction: jest.fn(),
      },
    });

    await expect(r.runTask(task(), new AbortController().signal)).rejects.toThrow(
      'invalid-task: id missing',
    );
  });

  // 测试 4: adapter.findWindow 返回 null → 抛 'window-not-found'
  it('case 4: findWindow 返回 null → 抛 window-not-found', async () => {
    const adapter = makeAdapter({
      findWindow: jest.fn().mockResolvedValue(null),
    });
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    await expect(r.runTask(task(), new AbortController().signal)).rejects.toThrow(
      'window-not-found',
    );
  });

  // 测试 5: adapter.preFlightCheck 抛出 'precondition-failed: not in target chat' → 透传
  it('case 5: preFlightCheck 抛出 precondition-failed → runtime 原样重抛', async () => {
    const adapter = makeAdapter({
      preFlightCheck: jest.fn().mockRejectedValue(
        new Error('precondition-failed: not in target chat'),
      ),
    });
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    await expect(r.runTask(task(), new AbortController().signal)).rejects.toThrow(
      'precondition-failed: not in target chat',
    );
  });

  // 测试 6: adapter.verifyResult 返回 false → 抛 'verify-failed'
  it('case 6: verifyResult 返回 false → 抛 verify-failed', async () => {
    const adapter = makeAdapter({
      verifyResult: jest.fn().mockResolvedValue(false),
    });
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    await expect(r.runTask(task(), new AbortController().signal)).rejects.toThrow(
      'verify-failed',
    );
  });

  // 测试 7: buildSteps 返回空数组 → 不执行任何 step，verifyResult 仍被调用，成功
  it('case 7: buildSteps 返回空数组 → steps===0，verify 仍调用，成功', async () => {
    const adapter = makeAdapter({
      buildSteps: jest.fn().mockResolvedValue([]),
      verifyResult: jest.fn().mockResolvedValue(true),
    });
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    const result = await r.runTask(task(), new AbortController().signal);
    expect(result.ok).toBe(true);
    expect(result.steps).toBe(0);
    expect(adapter.verifyResult).toHaveBeenCalledTimes(1);
    expect((r as any).actionExecutor.execute).not.toHaveBeenCalled();
  });

  // 测试 8: validateAction 在第 2 步抛出 → execute 只调用 1 次，之后抛错
  it('case 8: validateAction 在第 2 步抛出 → execute 调用 1 次，runtime 重抛', async () => {
    let callCount = 0;
    const adapter = makeAdapter();
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
      safetyGuard: {
        validateTaskShape: jest.fn(),
        validateAppId: jest.fn(),
        validateAction: jest.fn().mockImplementation(() => {
          callCount++;
          if (callCount === 2) {
            throw new Error('dangerous-key-blocked: delete');
          }
        }),
      },
    });

    await expect(r.runTask(task(), new AbortController().signal)).rejects.toThrow(
      'dangerous-key-blocked: delete',
    );

    // 第 1 步正常 execute，第 2 步 validateAction 前抛出
    expect((r as any).actionExecutor.execute).toHaveBeenCalledTimes(1);
  });

  // 测试 9: AbortSignal 在 runTask 开始时就已 abort → 抛 'aborted: <reason>'
  it('case 9: AbortSignal 已 abort → 抛 aborted: <reason>', async () => {
    const ac = new AbortController();
    ac.abort('task-cancelled');
    const r = makeRuntime();

    await expect(r.runTask(task(), ac.signal)).rejects.toThrow(/aborted: task-cancelled/);
  });

  // 测试 10: AbortSignal 在 step 1 execute 内 abort → step 2 不执行，抛 'aborted: user-cancelled'
  it('case 10: AbortSignal 在 step 1 execute 内 abort → step 2 不执行', async () => {
    const ac = new AbortController();
    const adapter = makeAdapter({
      buildSteps: jest.fn().mockResolvedValue([
        { type: 'wait', ms: 10 },
        { type: 'wait', ms: 10 },
      ]),
    });
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
      actionExecutor: {
        setInput: jest.fn(),
        execute: jest
          .fn()
          .mockImplementationOnce(async () => {
            ac.abort('user-cancelled');
          })
          .mockResolvedValue(undefined),
      },
    });

    await expect(r.runTask(task(), ac.signal)).rejects.toThrow(/aborted: user-cancelled/);
    expect((r as any).actionExecutor.execute).toHaveBeenCalledTimes(1);
  });

  // 测试 11: ctx.modelCalls 由 preFlightCheck mock 累积 → result.modelCalls > 0
  it('case 11: preFlightCheck 累积 ctx.modelCalls → result.modelCalls===3', async () => {
    const adapter = makeAdapter({
      preFlightCheck: jest.fn().mockImplementation(async (_t: any, ctx: any) => {
        ctx.modelCalls = 3;
      }),
    });
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    const result = await r.runTask(task(), new AbortController().signal);
    expect(result.modelCalls).toBe(3);
  });

  // 测试 12: durationMs 是非负数
  it('case 12: durationMs 是非负数', async () => {
    const adapter = makeAdapter();
    const r = makeRuntime({
      adapterRegistry: { get: jest.fn().mockReturnValue(adapter) },
    });

    const result = await r.runTask(task(), new AbortController().signal);
    expect(result.durationMs).toBeGreaterThanOrEqual(0);
  });
});
