import { describe, expect, it } from 'vitest';

import { TeamGraphAdapter } from '@renderer/features/agent-graph/adapters/TeamGraphAdapter';

import type { InboxMessage, TeamData, TeamTaskWithKanban } from '@shared/types/team';

function createBaseTeamData(
  overrides?: Partial<TeamData> & {
    tasks?: TeamTaskWithKanban[];
    messages?: InboxMessage[];
  }
): TeamData {
  return {
    teamName: 'my-team',
    config: {
      name: 'My Team',
      members: [{ name: 'team-lead' }, { name: 'alice' }, { name: 'bob' }],
      projectPath: '/repo',
    },
    members: [
      {
        name: 'team-lead',
        status: 'active',
        currentTaskId: null,
        taskCount: 0,
        lastActiveAt: null,
        messageCount: 0,
        agentType: 'team-lead',
      },
      {
        name: 'alice',
        status: 'active',
        currentTaskId: null,
        taskCount: 1,
        lastActiveAt: null,
        messageCount: 0,
      },
      {
        name: 'bob',
        status: 'active',
        currentTaskId: null,
        taskCount: 1,
        lastActiveAt: null,
        messageCount: 0,
      },
    ],
    tasks: [],
    messages: [],
    kanbanState: { teamName: 'my-team', reviewers: [], tasks: {} },
    processes: [],
    isAlive: true,
    ...overrides,
  };
}

describe('TeamGraphAdapter particles', () => {
  it('creates a message particle for a new incoming message from the newest message set', () => {
    const adapter = TeamGraphAdapter.create();
    const baseline = createBaseTeamData();
    adapter.adapt(baseline, 'my-team');

    const next = createBaseTeamData({
      messages: [
        {
          from: 'alice',
          to: 'team-lead',
          text: 'Please check the latest build output now',
          timestamp: '2026-03-28T19:00:01.000Z',
          read: false,
          messageId: 'msg-new',
        },
      ],
    });

    const graph = adapter.adapt(next, 'my-team');

    expect(graph.particles).toHaveLength(1);
    expect(graph.particles[0]).toMatchObject({
      kind: 'inbox_message',
      progress: 0,
      label: '✉ Please check the latest b…',
    });
  });

  it('creates a comment particle for the first new task comment with preview text', () => {
    const adapter = TeamGraphAdapter.create();
    const baseline = createBaseTeamData({
      tasks: [
        {
          id: 'task-1',
          displayId: '#1',
          subject: 'Investigate',
          owner: 'alice',
          status: 'in_progress',
          comments: [],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
    });
    adapter.adapt(baseline, 'my-team');

    const next = createBaseTeamData({
      tasks: [
        {
          id: 'task-1',
          displayId: '#1',
          subject: 'Investigate',
          owner: 'alice',
          status: 'in_progress',
          comments: [
            {
              id: 'comment-1',
              author: 'alice',
              text: 'Need clarification on the acceptance criteria before I continue',
              createdAt: '2026-03-28T19:00:02.000Z',
              type: 'regular',
            },
          ],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
    });

    const graph = adapter.adapt(next, 'my-team');

    expect(graph.particles).toHaveLength(1);
    expect(graph.particles[0]).toMatchObject({
      kind: 'task_comment',
      label: '💬 Need clarification on the…',
    });
  });

  it('creates a synthetic message edge for comments from non-owner participants', () => {
    const adapter = TeamGraphAdapter.create();
    const baseline = createBaseTeamData({
      tasks: [
        {
          id: 'task-2',
          displayId: '#2',
          subject: 'Fix regression',
          owner: 'bob',
          status: 'in_progress',
          comments: [],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
    });
    adapter.adapt(baseline, 'my-team');

    const next = createBaseTeamData({
      tasks: [
        {
          id: 'task-2',
          displayId: '#2',
          subject: 'Fix regression',
          owner: 'bob',
          status: 'in_progress',
          comments: [
            {
              id: 'comment-2',
              author: 'alice',
              text: 'I found the root cause, handing notes over now',
              createdAt: '2026-03-28T19:00:03.000Z',
              type: 'regular',
            },
          ],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
    });

    const graph = adapter.adapt(next, 'my-team');

    expect(graph.particles).toHaveLength(1);
    expect(graph.particles[0]).toMatchObject({
      kind: 'task_comment',
      label: '💬 I found the root cause, h…',
    });
    expect(
      graph.edges.some((edge) => edge.id === 'edge:msg:member:my-team:alice:task:my-team:task-2')
    ).toBe(true);
  });

  it('does not collapse two new inbox particles that share a timestamp but differ in content', () => {
    const adapter = TeamGraphAdapter.create();
    adapter.adapt(createBaseTeamData(), 'my-team');

    const next = createBaseTeamData({
      messages: [
        {
          from: 'alice',
          to: 'team-lead',
          text: 'First payload',
          timestamp: '2026-03-28T19:00:01.000Z',
          read: false,
        },
        {
          from: 'bob',
          to: 'team-lead',
          text: 'Second payload',
          timestamp: '2026-03-28T19:00:01.000Z',
          read: false,
        },
      ],
    });

    const graph = adapter.adapt(next, 'my-team');

    expect(graph.particles).toHaveLength(2);
    expect(graph.particles.every((particle) => particle.kind === 'inbox_message')).toBe(true);
  });

  it('creates particles for each newly appended task comment, not only the latest one', () => {
    const adapter = TeamGraphAdapter.create();
    const baseline = createBaseTeamData({
      tasks: [
        {
          id: 'task-4',
          displayId: '#4',
          subject: 'Burst comments',
          owner: 'alice',
          status: 'in_progress',
          comments: [],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
    });
    adapter.adapt(baseline, 'my-team');

    const next = createBaseTeamData({
      tasks: [
        {
          id: 'task-4',
          displayId: '#4',
          subject: 'Burst comments',
          owner: 'alice',
          status: 'in_progress',
          comments: [
            {
              id: 'comment-4a',
              author: 'alice',
              text: 'First burst comment',
              createdAt: '2026-03-28T19:00:06.000Z',
              type: 'regular',
            },
            {
              id: 'comment-4b',
              author: 'bob',
              text: 'Second burst comment',
              createdAt: '2026-03-28T19:00:07.000Z',
              type: 'regular',
            },
          ],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
    });

    const graph = adapter.adapt(next, 'my-team');

    expect(graph.particles).toHaveLength(2);
    expect(graph.particles.every((particle) => particle.kind === 'task_comment')).toBe(true);
  });

  it('maps the real lead name to the lead node for inbox messages and task comments', () => {
    const adapter = TeamGraphAdapter.create();
    const baseline = createBaseTeamData({
      config: {
        name: 'My Team',
        members: [{ name: 'olivia', agentType: 'lead' }, { name: 'alice' }],
        projectPath: '/repo',
      },
      members: [
        {
          name: 'olivia',
          status: 'active',
          currentTaskId: null,
          taskCount: 0,
          lastActiveAt: null,
          messageCount: 0,
          agentType: 'lead',
        },
        {
          name: 'alice',
          status: 'active',
          currentTaskId: null,
          taskCount: 1,
          lastActiveAt: null,
          messageCount: 0,
        },
      ],
      tasks: [
        {
          id: 'task-3',
          displayId: '#3',
          subject: 'Review notes',
          owner: 'alice',
          status: 'in_progress',
          comments: [],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
      messages: [],
    });
    adapter.adapt(baseline, 'my-team');

    const next = createBaseTeamData({
      config: baseline.config,
      members: baseline.members,
      tasks: [
        {
          id: 'task-3',
          displayId: '#3',
          subject: 'Review notes',
          owner: 'alice',
          status: 'in_progress',
          comments: [
            {
              id: 'comment-3',
              author: 'olivia',
              text: 'Please tighten the acceptance criteria before merge',
              createdAt: '2026-03-28T19:00:04.000Z',
              type: 'regular',
            },
          ],
          reviewState: 'none',
        } as TeamTaskWithKanban,
      ],
      messages: [
        {
          from: 'olivia',
          to: 'alice',
          text: 'Please pick this up next',
          timestamp: '2026-03-28T19:00:05.000Z',
          read: false,
          messageId: 'lead-msg-1',
        },
      ],
    });

    const graph = adapter.adapt(next, 'my-team');

    expect(graph.particles).toHaveLength(2);
    expect(graph.particles.map((particle) => particle.kind).sort()).toEqual([
      'inbox_message',
      'task_comment',
    ]);
  });

  it('creates inbox particles for all unseen messages, not only the newest 20', () => {
    const adapter = TeamGraphAdapter.create();
    adapter.adapt(createBaseTeamData(), 'my-team');

    const messages: InboxMessage[] = Array.from({ length: 25 }, (_, index) => ({
      from: index % 2 === 0 ? 'alice' : 'bob',
      to: 'team-lead',
      text: `Payload ${index + 1}`,
      timestamp: `2026-03-28T19:00:${String(index).padStart(2, '0')}.000Z`,
      read: false,
      messageId: `msg-${index + 1}`,
    }));

    const graph = adapter.adapt(createBaseTeamData({ messages }), 'my-team');

    expect(graph.particles).toHaveLength(25);
    expect(graph.particles.every((particle) => particle.kind === 'inbox_message')).toBe(true);
  });

  it('scopes inbox particle ids by team name to avoid cross-team collisions', () => {
    const adapter = TeamGraphAdapter.create();
    adapter.adapt(createBaseTeamData({ teamName: 'team-a' }), 'team-a');

    const graph = adapter.adapt(
      createBaseTeamData({
        teamName: 'team-a',
        messages: [
          {
            from: 'alice',
            to: 'team-lead',
            text: 'Same payload',
            timestamp: '2026-03-28T19:10:00.000Z',
            read: false,
            messageId: 'shared-msg',
          },
        ],
      }),
      'team-a'
    );

    expect(graph.particles[0]?.id).toBe('particle:msg:team-a:shared-msg');
  });

  it('does not return a cached snapshot when message content changes at the same list length', () => {
    const adapter = TeamGraphAdapter.create();
    adapter.adapt(
      createBaseTeamData({
        messages: [
          {
            from: 'alice',
            to: 'team-lead',
            text: 'Old payload',
            timestamp: '2026-03-28T19:20:00.000Z',
            read: false,
            messageId: 'msg-old',
          },
        ],
      }),
      'my-team'
    );

    const graph = adapter.adapt(
      createBaseTeamData({
        messages: [
          {
            from: 'bob',
            to: 'team-lead',
            text: 'New payload',
            timestamp: '2026-03-28T19:20:01.000Z',
            read: false,
            messageId: 'msg-new',
          },
        ],
      }),
      'my-team'
    );

    expect(graph.particles).toHaveLength(1);
    expect(graph.particles[0]).toMatchObject({
      id: 'particle:msg:my-team:msg-new',
      kind: 'inbox_message',
    });
  });

  it('does not return a cached snapshot when a member status changes at the same list size', () => {
    const adapter = TeamGraphAdapter.create();
    adapter.adapt(createBaseTeamData(), 'my-team');

    const graph = adapter.adapt(
      createBaseTeamData({
        members: [
          {
            name: 'team-lead',
            status: 'active',
            currentTaskId: null,
            taskCount: 0,
            lastActiveAt: null,
            messageCount: 0,
            agentType: 'team-lead',
          },
          {
            name: 'alice',
            status: 'idle',
            currentTaskId: null,
            taskCount: 1,
            lastActiveAt: null,
            messageCount: 0,
          },
          {
            name: 'bob',
            status: 'active',
            currentTaskId: null,
            taskCount: 1,
            lastActiveAt: null,
            messageCount: 0,
          },
        ],
      }),
      'my-team'
    );

    const alice = graph.nodes.find((node) => node.id === 'member:my-team:alice');
    expect(alice?.state).toBe('idle');
  });
});
