import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import Fastify from 'fastify';
import { agentRuntimeRoutes } from './routes.js';
import { setIpcBridge, type IpcBridge } from '../../lib/ipc-bridge.js';

vi.mock('../../lib/config.js', () => ({
  config: {
    PLATFORM_SERVICE_URL: 'http://mock-platform:4003',
  },
}));

vi.mock('../../lib/product-config.js', () => ({
  PRODUCT_ID: 'clawcowork',
  productConfig: {
    backendPort: 4009,
  },
}));

vi.mock('../../lib/request-context.js', () => ({
  getUserId: vi.fn(() => 'demo-user'),
}));

const mockFetch = vi.fn();
vi.stubGlobal('fetch', mockFetch);

let app: ReturnType<typeof Fastify>;
const call = vi.fn();

beforeEach(async () => {
  setIpcBridge({
    isRunning: true,
    call,
  } as unknown as IpcBridge);

  app = Fastify({ logger: false });
  app.decorateRequest('jwtPayload', null);
  await app.register(agentRuntimeRoutes);
  call.mockReset();
  mockFetch.mockReset();
});

afterEach(async () => {
  setIpcBridge(null);
  await app.close();
});

describe('agent runtime routes', () => {
  it('projects IPC sessions into shared AgentSession objects', async () => {
    call.mockResolvedValue({
      result: {
        sessions: [
          {
            id: 'sess-1',
            userId: 'demo-user',
            currentTaskId: 'task-1',
            createdAt: '2026-04-04T08:00:00.000Z',
            updatedAt: '2026-04-04T08:05:00.000Z',
            artifactRefs: ['artifact://notelett/note-1'],
            memoryRefs: ['memory://mindlyst/memory-1'],
            approvalRefs: ['approval://cowork/ap-1'],
          },
        ],
      },
    });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/sessions' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.sessions[0]).toMatchObject({
      sessionId: 'sess-1',
      userId: 'demo-user',
      productId: 'clawcowork',
      currentTaskId: 'task-1',
      status: 'active',
      artifactRefs: ['artifact://notelett/note-1'],
      memoryRefs: ['memory://mindlyst/memory-1'],
      approvalRefs: ['approval://cowork/ap-1'],
    });
  });

  it('projects IPC tasks into shared AgentRun objects', async () => {
    call.mockResolvedValue({
      result: {
        tasks: [
          {
            id: 'task-1',
            status: 'running',
            createdAt: '2026-04-04T08:00:00.000Z',
            updatedAt: '2026-04-04T08:10:00.000Z',
            sessionId: 'sess-1',
            eventId: 'evt_cowork_task_task-1',
            checkpointArtifactId: 'artifact://notelett/note-1',
          },
        ],
      },
    });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/runs' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.runs[0]).toMatchObject({
      runId: 'task-1',
      sessionId: 'sess-1',
      productId: 'clawcowork',
      status: 'running',
      checkpointArtifactId: 'artifact://notelett/note-1',
      correlationId: 'evt_cowork_task_task-1',
    });
  });

  it('projects pending IPC tasks into queued AgentRun objects', async () => {
    call.mockResolvedValue({
      result: {
        tasks: [
          {
            id: 'task-queued',
            status: 'pending',
            createdAt: '2026-04-04T08:00:00.000Z',
            updatedAt: '2026-04-04T08:10:00.000Z',
            sessionId: 'sess-queued',
          },
        ],
      },
    });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/runs' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.runs[0]).toMatchObject({
      runId: 'task-queued',
      sessionId: 'sess-queued',
      status: 'queued',
    });
  });

  it('projects IPC tasks into shared AgentTask objects', async () => {
    call.mockResolvedValue({
      result: {
        tasks: [
          {
            id: 'task-1',
            goal: 'Review the repository and summarize changes',
            status: 'pending',
            createdAt: '2026-04-04T08:00:00.000Z',
            updatedAt: '2026-04-04T08:10:00.000Z',
            sessionId: 'sess-1',
          },
        ],
      },
    });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/tasks' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.tasks[0]).toMatchObject({
      taskId: 'task-1',
      sessionId: 'sess-1',
      status: 'queued',
      title: 'Review the repository and summarize changes',
      intent: 'Review the repository and summarize changes',
    });
  });

  it('projects IPC tasks into shared AgentTodo objects', async () => {
    call
      .mockResolvedValueOnce({
        result: {
          tasks: [
            {
              id: 'task-1',
              goal: 'Review the repository and summarize changes',
              status: 'running',
              createdAt: '2026-04-04T08:00:00.000Z',
              updatedAt: '2026-04-04T08:10:00.000Z',
              sessionId: 'sess-1',
            },
          ],
        },
      })
      .mockResolvedValueOnce({
        result: {
          checkpoints: [
            {
              task_id: 'task-1',
              user_id: 'demo-user',
              goal: 'Review the repository and summarize changes',
              created_at: '2026-04-04T08:00:00.000Z',
              last_updated: '2026-04-04T08:10:00.000Z',
              completed: false,
              cancelled: false,
              completed_tool_calls: 3,
              error: null,
            },
          ],
        },
      });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/todos' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.todos[0]).toMatchObject({
      todoId: 'todo_task-1',
      sessionId: 'sess-1',
      status: 'in-progress',
      text: 'Review the repository and summarize changes',
    });
  });

  it('projects persisted checkpoints into shared AgentCheckpoint objects', async () => {
    call
      .mockResolvedValueOnce({
        result: {
          tasks: [
            {
              id: 'task-1',
              goal: 'Review the repository and summarize changes',
              status: 'pending',
              createdAt: '2026-04-04T08:00:00.000Z',
              updatedAt: '2026-04-04T08:10:00.000Z',
              sessionId: 'sess-1',
            },
          ],
        },
      })
      .mockResolvedValueOnce({
        result: {
          checkpoints: [
            {
              task_id: 'task-1',
              user_id: 'demo-user',
              goal: 'Review the repository and summarize changes',
              created_at: '2026-04-04T08:00:00.000Z',
              last_updated: '2026-04-04T08:10:00.000Z',
              completed: false,
              cancelled: false,
              completed_tool_calls: 2,
              checkpoint_artifact_id: 'artifact://notelett/note-1',
              artifact_refs: ['artifact://notelett/note-1'],
              memory_refs: ['memory://mindlyst/memory-1'],
              approval_refs: ['approval://cowork/ap-1'],
              error: null,
            },
          ],
        },
      });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/checkpoints' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.checkpoints[0]).toMatchObject({
      checkpointId: 'ckpt_task-1',
      sessionId: 'sess-1',
      runId: 'task-1',
      userId: 'demo-user',
      statusAtCapture: 'queued',
      resumeToken: 'task-1',
      checkpointArtifactId: 'artifact://notelett/note-1',
      artifactRefs: ['artifact://notelett/note-1'],
      memoryRefs: ['memory://mindlyst/memory-1'],
      approvalRefs: ['approval://cowork/ap-1'],
    });
  });

  it('returns richer checkpoint drill-in detail for one persisted checkpoint', async () => {
    call
      .mockResolvedValueOnce({
        result: {
          tasks: [
            {
              id: 'task-1',
              goal: 'Review the repository and summarize changes',
              status: 'running',
              createdAt: '2026-04-04T08:00:00.000Z',
              updatedAt: '2026-04-04T08:10:00.000Z',
              sessionId: 'sess-1',
            },
          ],
        },
      })
      .mockResolvedValueOnce({
        result: {
          checkpoints: [
            {
              task_id: 'task-1',
              user_id: 'demo-user',
              goal: 'Review the repository and summarize changes',
              folder: '/tmp/workspace',
              model: 'gpt-5.4',
              created_at: '2026-04-04T08:00:00.000Z',
              last_updated: '2026-04-04T08:10:00.000Z',
              completed: false,
              cancelled: false,
              completed_tool_calls: 2,
              checkpoint_artifact_id: 'artifact://notelett/note-1',
              artifact_refs: ['artifact://notelett/note-1'],
              memory_refs: ['memory://mindlyst/memory-1'],
              approval_refs: ['approval://cowork/ap-1'],
              event_id: 'evt_cowork_task_task-1',
              correlation_id: 'evt_cowork_task_task-1',
              error: null,
            },
          ],
        },
      });

    const res = await app.inject({
      method: 'GET',
      url: '/api/agent-runtime/checkpoints/ckpt_task-1',
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.checkpoint).toMatchObject({
      checkpointId: 'ckpt_task-1',
      runId: 'task-1',
      sessionId: 'sess-1',
      checkpointArtifactId: 'artifact://notelett/note-1',
    });
    expect(body.detail).toMatchObject({
      goal: 'Review the repository and summarize changes',
      folder: '/tmp/workspace',
      model: 'gpt-5.4',
      completedToolCalls: 2,
      eventId: 'evt_cowork_task_task-1',
      correlationId: 'evt_cowork_task_task-1',
      source: {
        observationKind: 'persisted-checkpoint',
        projectionKind: 'agent-runtime-summary',
      },
    });
  });

  it('projects approval audit records into AgentApprovalCheckpoint objects', async () => {
    mockFetch
      .mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          records: [
            {
              id: 'audit-1',
              action: 'approval_granted',
              timestamp: '2026-04-04T09:00:00.000Z',
              details: {
                eventId: 'evt_cowork_approval_1',
                taskId: 'task-1',
                sessionId: 'sess-1',
                tool: 'rm',
                resolverSurface: 'desktop',
              },
            },
          ],
        }),
      })
      .mockResolvedValueOnce({
        ok: true,
        json: async () => ({ records: [] }),
      });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/approvals' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.approvals[0]).toMatchObject({
      approvalId: 'evt_cowork_approval_1',
      sessionId: 'sess-1',
      runId: 'task-1',
      status: 'approved',
      resolverSurface: 'desktop',
    });
  });

  it('projects audit records into AgentActionLog objects', async () => {
    mockFetch.mockResolvedValue({
      ok: true,
      json: async () => ({
        records: [
          {
            id: 'audit-2',
            action: 'task_completed',
            category: 'execution',
            timestamp: '2026-04-04T09:05:00.000Z',
            details: {
              eventId: 'evt_cowork_action_1',
              taskId: 'task-1',
              sessionId: 'sess-1',
              correlationId: 'corr-1',
              result: 'success',
            },
          },
        ],
      }),
    });

    const res = await app.inject({ method: 'GET', url: '/api/agent-runtime/actions' });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.payload);
    expect(body.actions[0]).toMatchObject({
      actionLogId: 'evt_cowork_action_1',
      sessionId: 'sess-1',
      runId: 'task-1',
      eventName: 'task_completed',
      actorType: 'agent',
      correlationId: 'corr-1',
    });
  });

  it('validates Cowork-targeted dispatch payloads', async () => {
    const res = await app.inject({
      method: 'POST',
      url: '/api/agent-runtime/dispatch/validate',
      payload: {
        dispatchId: 'dispatch-1',
        targetProductId: 'clawcowork',
        targetExecutor: 'cowork',
        userId: 'demo-user',
        title: 'Review workspace changes',
        intent: 'Summarize the repo changes and create a patch plan.',
        artifactRefs: [],
        memoryRefs: [],
        dispatchContext: {
          originSurface: 'desktop',
          originProductId: 'clawcowork',
          dispatchMode: 'interactive',
          initiatedAt: '2026-04-04T10:00:00.000Z',
        },
      },
    });

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.payload).valid).toBe(true);
  });

  it('rejects dispatch payloads for other products', async () => {
    const res = await app.inject({
      method: 'POST',
      url: '/api/agent-runtime/dispatch/validate',
      payload: {
        dispatchId: 'dispatch-1',
        targetProductId: 'other-product',
        targetExecutor: 'cowork',
        userId: 'demo-user',
        title: 'Review workspace changes',
        intent: 'Summarize the repo changes and create a patch plan.',
        artifactRefs: [],
        memoryRefs: [],
        dispatchContext: {
          originSurface: 'desktop',
          originProductId: 'clawcowork',
          dispatchMode: 'interactive',
          initiatedAt: '2026-04-04T10:00:00.000Z',
        },
      },
    });

    expect(res.statusCode).toBe(400);
  });
});
