/**
 * Unit + integration tests for Agent Performance Profiles.
 *
 * Unit tests verify that runEndSessionPipeline correctly refreshes
 * the materialized view and upserts an "agent" knowledge entity.
 *
 * Integration tests (skipped when not configured) call the real API.
 *
 * Run with: bun test packages/mcp-server/src/__tests__/agent-performance-profiles.test.ts
 */

import { describe, expect, mock, test } from "bun:test";

// ============================================================
// Module mocks (must be before imports)
// ============================================================

const mockWorkspaceId: string | null = "ws-test-123";
const mockProjectId: string | null = "proj-test-456";

mock.module("../config.js", () => ({
  getActiveWorkspaceId: () => mockWorkspaceId,
  getActiveProjectId: () => mockProjectId,
  isConfigured: () => true,
  loadConfig: () => {},
  getApiUrl: () => "http://localhost:54321",
  getApiKey: () => "test-key",
  getMemoryDir: () => null,
  getUserEmail: () => null,
  saveConfig: () => {},
  setActiveProject: () => {},
  setActiveWorkspace: () => {},
  resetClient: () => {},
}));

mock.module("../active-learning.js", () => ({
  extractLearnings: mock(async () => ({ count: 0, entityIds: [] })),
  extractMidSessionLearnings: mock(async () => ({ count: 0, entityIds: [] })),
  detectContradictions: mock(async () => ({ contradictions: [] })),
}));

mock.module("../context-assembly.js", () => ({
  recordContextFeedback: mock(async () => ({ adjusted: 0 })),
  assembleContext: mock(async () => ({ entities: [], markdown: "" })),
  cacheManifest: mock(() => {}),
  computeRelevanceScore: mock(() => 0),
  getCachedManifest: mock(() => null),
  mapToContextEntity: mock(() => ({})),
  trackSessionAssembly: mock(async () => ({})),
}));

mock.module("../lifecycle-maintenance.js", () => ({
  runLifecycleMaintenance: mock(async () => ({
    archived: 0,
    pruned: 0,
    promoted: 0,
    reviewed: 0,
  })),
}));

mock.module("../consolidation.js", () => ({
  consolidateMemories: mock(async () => ({ consolidated: 0 })),
}));

mock.module("../graph-expansion.js", () => ({
  autoExpandGraph: mock(async () => {}),
}));

// Import after mocking
const { runEndSessionPipeline } = await import("../server.js");

// ============================================================
// Helpers
// ============================================================

const flush = () => new Promise((r) => setTimeout(r, 100));

function makeMockClient() {
  const createdEntities: Array<Record<string, unknown>> = [];
  let nextId = 1;

  return {
    createdEntities,
    getCard: mock(async () => ({
      card: {
        title: "Test card",
        description: "desc",
        labels: [],
        subtasks: [],
      },
    })),
    removeLabelFromCard: mock(async () => ({ success: true })),
    createMemoryEntity: mock(async (data: Record<string, unknown>) => {
      const entity = { id: `entity-${nextId++}`, ...data };
      createdEntities.push(entity);
      return { entity };
    }),
    updateMemoryEntity: mock(
      async (entityId: string, updates: Record<string, unknown>) => ({
        entity: { id: entityId, ...updates },
      }),
    ),
    listMemoryEntities: mock(async () => ({
      entities: [] as unknown[],
      count: 0,
    })),
    searchMemoryEntities: mock(async () => ({
      entities: [] as unknown[],
      count: 0,
    })),
    getMemoryEntity: mock(async (id: string) => ({
      entity: { id, type: "agent" },
    })),
    deleteMemoryEntity: mock(async () => ({ success: true })),
    createMemoryRelation: mock(async () => ({ relation: {} })),
    refreshAgentProfiles: mock(async () => ({ refreshed: true })),
    getAgentProfile: mock(async () => ({
      profile: {
        agent_identifier: "claude-code",
        total_sessions: 10,
        completed_sessions: 8,
        paused_sessions: 1,
        blocked_sessions: 1,
        completion_rate_pct: 80,
        avg_active_duration_ms: 120000,
        avg_completion_progress: 95,
        first_session_at: "2026-01-01T00:00:00Z",
        last_session_at: "2026-03-05T00:00:00Z",
      },
    })),
  };
}

function makeDeps(overrides: Partial<Record<string, unknown>> = {}) {
  return {
    getClient: () => null,
    isConfigured: () => true,
    getActiveProjectId: () => mockProjectId,
    getActiveWorkspaceId: () =>
      overrides.workspaceId !== undefined
        ? overrides.workspaceId
        : mockWorkspaceId,
    setActiveProject: () => {},
    setActiveWorkspace: () => {},
    getApiUrl: () => "http://localhost",
    getMemoryDir: () => null,
    getUserEmail: () => null,
    saveConfig: () => {},
    resetClient: () => {},
  };
}

// ============================================================
// Unit Tests
// ============================================================

describe("Agent profile refresh in runEndSessionPipeline", () => {
  test("creates new entity when none exists", async () => {
    const client = makeMockClient();
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    expect(client.refreshAgentProfiles).toHaveBeenCalledWith(mockWorkspaceId);
    expect(client.getAgentProfile).toHaveBeenCalled();
    expect(client.createMemoryEntity).toHaveBeenCalled();

    const agentEntity = client.createdEntities.find((e) => e.type === "agent");
    expect(agentEntity).toBeTruthy();
    expect(agentEntity!.memory_tier).toBe("reference");
    expect(agentEntity!.confidence).toBe(1.0);
    expect(agentEntity!.title).toBe("Agent Profile: claude-code");
    expect(agentEntity!.tags as string[]).toContain("agent-profile");
    expect(agentEntity!.tags as string[]).toContain("claude-code");
    expect(agentEntity!.content as string).toContain(
      "## claude-code Performance Profile",
    );
  });

  test("updates existing entity by title match", async () => {
    const client = makeMockClient();
    client.listMemoryEntities = mock(async () => ({
      entities: [
        {
          id: "existing-1",
          title: "Agent Profile: claude-code",
          agent_identifier: "claude-code",
        },
      ],
      count: 1,
    }));
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    expect(client.updateMemoryEntity).toHaveBeenCalledWith(
      "existing-1",
      expect.objectContaining({
        content: expect.stringContaining("## claude-code Performance Profile"),
        confidence: 1.0,
      }),
    );
    // Should NOT create a new entity
    const agentEntities = client.createdEntities.filter(
      (e) => e.type === "agent",
    );
    expect(agentEntities).toHaveLength(0);
  });

  test("updates existing entity by agent_identifier match", async () => {
    const client = makeMockClient();
    client.listMemoryEntities = mock(async () => ({
      entities: [
        {
          id: "existing-2",
          title: "Old Title",
          agent_identifier: "claude-code",
        },
      ],
      count: 1,
    }));
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    expect(client.updateMemoryEntity).toHaveBeenCalledWith(
      "existing-2",
      expect.objectContaining({
        confidence: 1.0,
      }),
    );
  });

  test("skips when workspace is null", async () => {
    const client = makeMockClient();
    const deps = makeDeps({ workspaceId: null });

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    expect(client.refreshAgentProfiles).not.toHaveBeenCalled();
  });

  test('skips when agent is "unknown"', async () => {
    const client = makeMockClient();
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "unknown",
        agent_name: "Unknown",
      },
    );
    await flush();

    expect(client.refreshAgentProfiles).not.toHaveBeenCalled();
  });

  test("skips when sessionData is undefined", async () => {
    const client = makeMockClient();
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
    );
    await flush();

    // agent_identifier defaults to "unknown" when sessionData is undefined
    expect(client.refreshAgentProfiles).not.toHaveBeenCalled();
  });

  test("non-fatal on refresh failure", async () => {
    const client = makeMockClient();
    client.refreshAgentProfiles = mock(async () => {
      throw new Error("Refresh service down");
    });
    const deps = makeDeps();

    // Should not throw
    const result = await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    // Pipeline still returns normally
    expect(result).toBeTruthy();
    expect(typeof result.learningsExtracted).toBe("number");
  });

  test("null profile skips entity upsert", async () => {
    const client = makeMockClient();
    client.getAgentProfile = mock(async () => ({ profile: null }));
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    expect(client.refreshAgentProfiles).toHaveBeenCalled();
    // No agent entity should be created since profile is null
    const agentEntities = client.createdEntities.filter(
      (e) => e.type === "agent",
    );
    expect(agentEntities).toHaveLength(0);
  });

  test("content has correct markdown", async () => {
    const client = makeMockClient();
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    const agentEntity = client.createdEntities.find((e) => e.type === "agent");
    expect(agentEntity).toBeTruthy();
    const content = agentEntity!.content as string;
    expect(content).toContain("## claude-code Performance Profile");
    expect(content).toContain("**Total sessions:**");
    expect(content).toContain("**Completed:**");
    expect(content).toContain("**Paused:**");
    expect(content).toContain("**Blocked:**");
    expect(content).toContain("**Avg duration:**");
    expect(content).toContain("**First session:**");
    expect(content).toContain("**Last session:**");
  });

  test("works with paused sessions", async () => {
    const client = makeMockClient();
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "paused",
      50,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    expect(client.refreshAgentProfiles).toHaveBeenCalled();
    expect(client.getAgentProfile).toHaveBeenCalled();
  });

  test("metadata contains profile data", async () => {
    const client = makeMockClient();
    const deps = makeDeps();

    await runEndSessionPipeline(
      client as never,
      deps as never,
      "card-1",
      "completed",
      100,
      {
        agent_identifier: "claude-code",
        agent_name: "Claude Code",
      },
    );
    await flush();

    const agentEntity = client.createdEntities.find((e) => e.type === "agent");
    expect(agentEntity).toBeTruthy();
    const metadata = agentEntity!.metadata as Record<string, unknown>;
    expect(metadata.total_sessions).toBe(10);
    expect(metadata.completion_rate_pct).toBe(80);
    expect(metadata.completed_sessions).toBe(8);
    expect(metadata.avg_active_duration_ms).toBe(120000);
  });
});

// ============================================================
// Integration Tests (skipped when not configured)
// ============================================================

// Integration tests are skipped in this file because mock.module() overrides
// the real config. Run integration-memory-crud.test.ts for live API tests,
// or use MCP tools directly for agent profile integration verification.
describe.skip("Agent Performance Profiles - Integration (run via MCP tools)", () => {
  test("refreshAgentProfiles returns { refreshed: true }", () => {});
  test('getAgentProfile("claude-code") returns profile or null', () => {});
  test('getAgentProfile("non-existent-xyz") returns null profile', () => {});
  test("listAgentProfiles returns array", () => {});
  test("profile shape has expected fields", () => {});
  test("entity round-trip: create agent entity → recall by type → cleanup", () => {});
});
