import {
  deriveMemberLaunchState,
  isAutoClearableLaunchFailureReason,
  isBootstrapCheckInTimeoutFailureReason,
  isBootstrapInstructionPromptFailureReason,
  isBootstrapMcpResourceReadFailureReason,
  isCliProvisionedButNotAliveFailureReason,
  isConfigRegistrationFailureReason,
  isLaunchCleanupBootstrapIncompleteFailureReason,
  isLaunchGraceWindowFailureReason,
  isNeverSpawnedDuringLaunchReason,
  isOpenCodeBridgeLaunchFailureReason,
  isProcessTableUnavailableFailureReason,
  isProvisionedButNotAliveFailureReason,
  isRegisteredRuntimeMetadataFailureReason,
  stripProcessTableUnavailableDiagnosticSuffix,
} from '@main/services/team/provisioning/TeamProvisioningLaunchFailurePolicy';
import { isBootstrapConfirmedProvisionedButNotAliveFailure } from '@shared/utils/teamLaunchFailureReason';
import { describe, expect, it } from 'vitest';

describe('TeamProvisioningLaunchFailurePolicy', () => {
  it('recognizes exact launch failure reasons that are safe to auto-clear', () => {
    expect(isNeverSpawnedDuringLaunchReason(' Teammate was never spawned during launch. ')).toBe(
      true
    );
    expect(
      isLaunchGraceWindowFailureReason('Teammate did not join within the launch grace window.')
    ).toBe(true);
    expect(
      isConfigRegistrationFailureReason(
        'Teammate was not registered in config.json during launch. Persistent spawn failed.'
      )
    ).toBe(true);
    expect(
      isOpenCodeBridgeLaunchFailureReason('OpenCode bridge reported member launch failure')
    ).toBe(true);
    expect(
      isRegisteredRuntimeMetadataFailureReason('registered runtime metadata without live process')
    ).toBe(true);
    expect(
      isProvisionedButNotAliveFailureReason(
        'CLI process exited (code 1) \u2014 team provisioned but not alive'
      )
    ).toBe(true);
    expect(
      isProvisionedButNotAliveFailureReason(
        'CLI process exited (code unknown) - team provisioned but not alive; process table unavailable'
      )
    ).toBe(true);
    expect(
      isCliProvisionedButNotAliveFailureReason(
        'CLI process exited (code ?) - team provisioned but not alive'
      )
    ).toBe(true);
  });

  it('recognizes bootstrap-specific failure reasons without accepting unrelated text', () => {
    expect(
      isBootstrapMcpResourceReadFailureReason(
        'resources/read failed for member_briefing: MCP error method not found'
      )
    ).toBe(true);
    expect(
      isBootstrapMcpResourceReadFailureReason('resources/read failed for other resource')
    ).toBe(false);
    expect(
      isBootstrapCheckInTimeoutFailureReason(
        'Teammate was registered but did not bootstrap-confirm before timeout.'
      )
    ).toBe(true);
    expect(
      isBootstrapCheckInTimeoutFailureReason(
        'Bootstrap prompt was submitted, but teammate did not bootstrap-confirm before submitted-confirmation timeout (3m). Last transport stage: bootstrap_submitted'
      )
    ).toBe(true);
    expect(
      isBootstrapCheckInTimeoutFailureReason(
        'Bootstrap prompt was submitted, but teammate did not bootstrap-confirm before timeout.'
      )
    ).toBe(false);
    expect(
      isBootstrapInstructionPromptFailureReason(
        'You are bootstrapping into team atlas. Your first action is to call the MCP tool member_briefing.'
      )
    ).toBe(true);
    expect(
      isLaunchCleanupBootstrapIncompleteFailureReason(
        'Launch ended before teammate bootstrap completed. Runtime process was alive after bootstrap failure'
      )
    ).toBe(true);
  });

  it('handles process-table unavailable reasons and suffixes conservatively', () => {
    expect(isProcessTableUnavailableFailureReason('process table unavailable')).toBe(true);
    expect(
      isProcessTableUnavailableFailureReason(
        'runtime pid could not be verified because process table is unavailable'
      )
    ).toBe(true);
    expect(
      isProcessTableUnavailableFailureReason('runtime failed; process table unavailable')
    ).toBe(false);
    expect(
      stripProcessTableUnavailableDiagnosticSuffix(
        'Teammate did not join within the launch grace window.; process table unavailable'
      )
    ).toBe('Teammate did not join within the launch grace window.');
  });

  it('keeps auto-clear policy narrow but accepts known recoverable suffixes', () => {
    expect(isAutoClearableLaunchFailureReason('Teammate was never spawned during launch.')).toBe(
      true
    );
    expect(isAutoClearableLaunchFailureReason('process table is unavailable')).toBe(true);
    expect(
      isAutoClearableLaunchFailureReason(
        'Teammate did not join within the launch grace window.; process table unavailable'
      )
    ).toBe(true);
    expect(
      isAutoClearableLaunchFailureReason(
        'Bootstrap prompt was submitted, but teammate did not bootstrap-confirm before submitted-confirmation timeout (3m). Last transport stage: bootstrap_submitted'
      )
    ).toBe(true);
    expect(
      isAutoClearableLaunchFailureReason(
        'Bootstrap prompt was submitted, but teammate did not bootstrap-confirm before timeout.'
      )
    ).toBe(false);
    expect(
      isAutoClearableLaunchFailureReason(
        'CLI process exited (code 1) \u2014 team provisioned but not alive'
      )
    ).toBe(false);
    expect(isAutoClearableLaunchFailureReason('model not found')).toBe(false);
    expect(isAutoClearableLaunchFailureReason()).toBe(false);
  });

  it('requires bootstrap proof before treating provisioned-but-not-alive as healed', () => {
    const reason = 'CLI process exited (code 1) \u2014 team provisioned but not alive';

    expect(
      isBootstrapConfirmedProvisionedButNotAliveFailure({
        status: 'error',
        launchState: 'failed_to_start',
        hardFailure: true,
        error: reason,
        bootstrapConfirmed: true,
      })
    ).toBe(true);

    expect(
      isBootstrapConfirmedProvisionedButNotAliveFailure({
        status: 'error',
        launchState: 'failed_to_start',
        hardFailure: true,
        hardFailureReason: 'model not found',
        error: reason,
        bootstrapConfirmed: true,
      })
    ).toBe(false);

    expect(
      isBootstrapConfirmedProvisionedButNotAliveFailure({
        status: 'error',
        launchState: 'failed_to_start',
        hardFailure: true,
        hardFailureReason: reason,
        bootstrapConfirmed: false,
        livenessKind: 'registered_only',
      })
    ).toBe(false);

    expect(
      isBootstrapConfirmedProvisionedButNotAliveFailure({
        status: 'error',
        launchState: 'failed_to_start',
        hardFailure: true,
        runtimeDiagnostic: reason,
        bootstrapConfirmed: true,
      })
    ).toBe(true);
  });

  it('derives member launch state by the existing precedence order', () => {
    expect(deriveMemberLaunchState({ skippedForLaunch: true, hardFailure: true })).toBe(
      'skipped_for_launch'
    );
    expect(deriveMemberLaunchState({ hardFailure: true, bootstrapConfirmed: true })).toBe(
      'failed_to_start'
    );
    expect(deriveMemberLaunchState({ bootstrapConfirmed: true })).toBe('confirmed_alive');
    expect(deriveMemberLaunchState({ pendingPermissionRequestIds: ['req-1'] })).toBe(
      'runtime_pending_permission'
    );
    expect(deriveMemberLaunchState({ runtimeAlive: true })).toBe('runtime_pending_bootstrap');
    expect(deriveMemberLaunchState({ agentToolAccepted: true })).toBe('runtime_pending_bootstrap');
    expect(deriveMemberLaunchState({})).toBe('starting');
  });
});
