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

import {
  resolveTeamMemberRuntimeLiveness,
  sanitizeProcessCommandForDiagnostics,
} from '@main/services/team/TeamRuntimeLivenessResolver';

const NOW = '2026-04-24T12:00:00.000Z';

describe('resolveTeamMemberRuntimeLiveness', () => {
  it('classifies tmux shell panes as weak shell-only evidence', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'bob',
      agentId: 'agent-bob',
      backendType: 'tmux',
      tmuxPaneId: '%1',
      pane: { paneId: '%1', panePid: 100, currentCommand: 'zsh' },
      processRows: [{ pid: 100, ppid: 1, command: 'zsh' }],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(false);
    expect(result.livenessKind).toBe('shell_only');
    expect(result.pidSource).toBe('tmux_pane');
    expect(result.pid).toBe(100);
  });

  it('promotes a verified team and agent process to strong runtime evidence', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'alice',
      agentId: 'agent-alice',
      backendType: 'tmux',
      processRows: [
        {
          pid: 222,
          ppid: 1,
          command: 'node runtime --team-name demo --agent-id agent-alice',
        },
      ],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(true);
    expect(result.livenessKind).toBe('runtime_process');
    expect(result.pidSource).toBe('agent_process_table');
    expect(result.pid).toBe(222);
  });

  it('keeps a verified process pid visible after bootstrap is confirmed', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'alice',
      agentId: 'agent-alice',
      backendType: 'tmux',
      trackedSpawnStatus: {
        status: 'online',
        launchState: 'confirmed_alive',
        agentToolAccepted: true,
        runtimeAlive: true,
        bootstrapConfirmed: true,
        hardFailure: false,
        updatedAt: NOW,
      },
      processRows: [
        {
          pid: 222,
          ppid: 1,
          command: 'node runtime --team-name demo --agent-id agent-alice',
        },
      ],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(true);
    expect(result.livenessKind).toBe('runtime_process');
    expect(result.pidSource).toBe('agent_process_table');
    expect(result.pid).toBe(222);
  });

  it('keeps a non-shell tmux descendant without identity as a candidate', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'jack',
      agentId: 'agent-jack',
      backendType: 'tmux',
      tmuxPaneId: '%2',
      pane: { paneId: '%2', panePid: 300, currentCommand: 'zsh' },
      processRows: [
        { pid: 300, ppid: 1, command: 'zsh' },
        { pid: 301, ppid: 300, command: 'node helper.js' },
      ],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(false);
    expect(result.livenessKind).toBe('runtime_process_candidate');
    expect(result.pidSource).toBe('tmux_child');
    expect(result.pid).toBe(301);
  });

  it('keeps a live OpenCode runtime pid as candidate until bootstrap is confirmed', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'bob',
      providerId: 'opencode',
      persistedRuntimePid: 404,
      persistedRuntimeSessionId: 'session-bob',
      processRows: [{ pid: 404, ppid: 1, command: 'opencode runtime host' }],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(false);
    expect(result.livenessKind).toBe('runtime_process_candidate');
    expect(result.pidSource).toBe('opencode_bridge');
    expect(result.pid).toBe(404);
    expect(result.runtimeDiagnostic).toBe(
      'OpenCode runtime process detected, but teammate bootstrap is not confirmed'
    );
  });

  it('promotes a live OpenCode runtime pid after bootstrap confirmation', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'bob',
      providerId: 'opencode',
      persistedRuntimePid: 404,
      persistedRuntimeSessionId: 'session-bob',
      trackedSpawnStatus: {
        status: 'online',
        launchState: 'confirmed_alive',
        agentToolAccepted: true,
        runtimeAlive: true,
        bootstrapConfirmed: true,
        hardFailure: false,
        updatedAt: NOW,
      },
      processRows: [{ pid: 404, ppid: 1, command: 'opencode runtime host' }],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(true);
    expect(result.livenessKind).toBe('runtime_process');
    expect(result.pidSource).toBe('opencode_bridge');
    expect(result.pid).toBe(404);
  });

  it('does not trust an OpenCode runtime pid reused by an unrelated process', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'bob',
      providerId: 'opencode',
      persistedRuntimePid: 404,
      persistedRuntimeSessionId: 'session-bob',
      processRows: [{ pid: 404, ppid: 1, command: 'node unrelated-worker.js' }],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(false);
    expect(result.livenessKind).toBe('runtime_process_candidate');
    expect(result.pidSource).toBe('opencode_bridge');
    expect(result.runtimeDiagnostic).toBe(
      'OpenCode runtime pid is alive, but process identity is unverified'
    );
  });

  it('does not let a reused OpenCode runtime pid downgrade committed bootstrap evidence', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'bob',
      providerId: 'opencode',
      persistedRuntimePid: 404,
      persistedRuntimeSessionId: 'session-bob',
      trackedSpawnStatus: {
        status: 'online',
        launchState: 'confirmed_alive',
        agentToolAccepted: true,
        runtimeAlive: true,
        bootstrapConfirmed: true,
        hardFailure: false,
        updatedAt: NOW,
      },
      processRows: [{ pid: 404, ppid: 1, command: 'node unrelated-worker.js' }],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(true);
    expect(result.livenessKind).toBe('confirmed_bootstrap');
    expect(result.pidSource).toBe('runtime_bootstrap');
    expect(result.pid).toBeUndefined();
    expect(result.diagnostics).toContain(
      'bootstrap confirmed despite runtime pid identity mismatch'
    );
  });

  it('does not trust a stale persisted pid without current process identity', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'tom',
      persistedRuntimePid: 444,
      processRows: [{ pid: 555, ppid: 1, command: 'node other.js' }],
      processTableAvailable: true,
      nowIso: NOW,
    });

    expect(result.alive).toBe(false);
    expect(result.livenessKind).toBe('stale_metadata');
    expect(result.pidSource).toBe('persisted_metadata');
  });

  it('does not treat a persisted pid as stale when the process table is unavailable', () => {
    const result = resolveTeamMemberRuntimeLiveness({
      teamName: 'demo',
      memberName: 'tom',
      persistedRuntimePid: 444,
      processRows: [],
      processTableAvailable: false,
      nowIso: NOW,
    });

    expect(result.alive).toBe(false);
    expect(result.livenessKind).toBe('registered_only');
    expect(result.pidSource).toBe('persisted_metadata');
    expect(result.diagnostics).toContain('process table unavailable');
  });

  it('redacts common secret flags in diagnostics commands', () => {
    expect(
      sanitizeProcessCommandForDiagnostics('node runtime --api-key sk-123 --token=abc --safe ok')
    ).toBe('node runtime --api-key [redacted] --token=[redacted] --safe ok');
  });
});
