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

import {
  buildReusableProviderPrepareModelResults,
  mergeReusableProviderPrepareModelResults,
  runProviderPrepareDiagnostics,
} from '@renderer/components/team/dialogs/providerPrepareDiagnostics';
import { DEFAULT_PROVIDER_MODEL_SELECTION } from '@shared/utils/providerModelSelection';

import type { TeamProviderId, TeamProvisioningPrepareResult } from '@shared/types';

function createDeferred<T>(): {
  promise: Promise<T>;
  resolve: (value: T) => void;
} {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((nextResolve) => {
    resolve = nextResolve;
  });
  return { promise, resolve };
}

describe('runProviderPrepareDiagnostics', () => {
  it('does not keep transient note results in the reusable cache', () => {
    expect(
      buildReusableProviderPrepareModelResults({
        'gpt-5.4': {
          status: 'ready',
          line: '5.4 - verified',
          warningLine: null,
        },
        'gpt-5.3-codex': {
          status: 'notes',
          line: '5.3 Codex - check failed - Model verification timed out',
          warningLine: '5.3 Codex - check failed - Model verification timed out',
        },
        'gpt-5.2-codex': {
          status: 'failed',
          line: '5.2 Codex - unavailable - Not available on this Codex native runtime',
          warningLine: null,
        },
      })
    ).toEqual({
      'gpt-5.4': {
        status: 'ready',
        line: '5.4 - verified',
        warningLine: null,
      },
      'gpt-5.2-codex': {
        status: 'failed',
        line: '5.2 Codex - unavailable - Not available on this Codex native runtime',
        warningLine: null,
      },
    });
  });

  it('merges reusable model results without dropping earlier cache entries', () => {
    expect(
      mergeReusableProviderPrepareModelResults(
        {
          'gpt-5.4': {
            status: 'ready',
            line: '5.4 - verified',
            warningLine: null,
          },
        },
        {
          'gpt-5.4-mini': {
            status: 'ready',
            line: '5.4 Mini - verified',
            warningLine: null,
          },
          'gpt-5.3-codex': {
            status: 'notes',
            line: '5.3 Codex - check failed - Model verification timed out',
            warningLine: '5.3 Codex - check failed - Model verification timed out',
          },
        }
      )
    ).toEqual({
      'gpt-5.4': {
        status: 'ready',
        line: '5.4 - verified',
        warningLine: null,
      },
      'gpt-5.4-mini': {
        status: 'ready',
        line: '5.4 Mini - verified',
        warningLine: null,
      },
    });
  });

  it('removes a stale reusable model result when the latest result is advisory', () => {
    expect(
      mergeReusableProviderPrepareModelResults(
        {
          'gpt-5.4': {
            status: 'ready',
            line: '5.4 - verified',
            warningLine: null,
          },
          'gpt-5.2-codex': {
            status: 'failed',
            line: '5.2 Codex - unavailable - Not available on this Codex native runtime',
            warningLine: null,
          },
        },
        {
          'gpt-5.2-codex': {
            status: 'notes',
            line: '5.2 Codex - check failed - Model verification timed out',
            warningLine: '5.2 Codex - check failed - Model verification timed out',
          },
        }
      )
    ).toEqual({
      'gpt-5.4': {
        status: 'ready',
        line: '5.4 - verified',
        warningLine: null,
      },
    });
  });

  it('returns a failed provider result immediately when runtime preflight fails', async () => {
    const prepareProvisioning = vi
      .fn<
        (
          cwd?: string,
          providerId?: TeamProviderId,
          providerIds?: TeamProviderId[],
          selectedModels?: string[],
          limitContext?: boolean,
          modelVerificationMode?: 'compatibility' | 'deep'
        ) => Promise<TeamProvisioningPrepareResult>
      >()
      .mockResolvedValue({
        ready: false,
        message: 'Codex runtime is not authenticated.',
      });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.4'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual(['Codex runtime is not authenticated.']);
    expect(prepareProvisioning).toHaveBeenCalledTimes(1);
  });

  it('batches uncached model probes per provider and keeps failures scoped to the affected model', async () => {
    const deferredBatch = createDeferred<TeamProvisioningPrepareResult>();
    const progressUpdates: Array<{
      status: 'checking' | 'ready' | 'notes' | 'failed';
      details: string[];
      completedCount: number;
      totalCount: number;
    }> = [];

    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      expect(selectedModels).toEqual(['gpt-5.4', 'gpt-5.2-codex']);
      return deferredBatch.promise;
    });

    const resultPromise = runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.4', 'gpt-5.2-codex'],
      prepareProvisioning,
      onModelProgress: (progress) => progressUpdates.push(progress),
    });

    await Promise.resolve();
    expect(progressUpdates[0]).toEqual({
      status: 'checking',
      completedCount: 0,
      totalCount: 2,
      details: ['5.4 - checking...', '5.2 Codex - checking...'],
    });

    deferredBatch.resolve({
      ready: false,
      message: 'Some provider runtimes are not ready',
      details: ['Selected model gpt-5.4 verified for launch.'],
      warnings: [
        "Selected model gpt-5.2-codex is unavailable. The 'gpt-5.2-codex' model is not supported when using Codex with a ChatGPT account.",
      ],
    });
    const result = await resultPromise;

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      '5.4 - verified',
      '5.2 Codex - unavailable - Not available on this Codex native runtime',
    ]);
    expect(progressUpdates.at(-1)).toEqual({
      status: 'failed',
      completedCount: 2,
      totalCount: 2,
      details: [
        '5.4 - verified',
        '5.2 Codex - unavailable - Not available on this Codex native runtime',
      ],
    });
    expect(prepareProvisioning).toHaveBeenCalledTimes(1);
  });

  it('runs OpenCode uncached selected models through compatibility first and deep verification second', async () => {
    const deferredCompatibility = createDeferred<TeamProvisioningPrepareResult>();
    const deferredDeep = createDeferred<TeamProvisioningPrepareResult>();
    const progressUpdates: Array<{
      status: 'checking' | 'ready' | 'notes' | 'failed';
      details: string[];
      completedCount: number;
      totalCount: number;
    }> = [];

    const prepareProvisioning = vi.fn(
      (
        _cwd?: string,
        _providerId?: TeamProviderId,
        _providerIds?: TeamProviderId[],
        selectedModels?: string[],
        _limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => {
        if (modelVerificationMode === 'compatibility') {
          expect(selectedModels).toEqual([
            'opencode/minimax-m2.5-free',
            'opencode/nemotron-3-super-free',
          ]);
          return deferredCompatibility.promise;
        }
        expect(modelVerificationMode).toBe('deep');
        expect(selectedModels).toEqual([
          'opencode/minimax-m2.5-free',
          'opencode/nemotron-3-super-free',
        ]);
        return deferredDeep.promise;
      }
    );

    const resultPromise = runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/minimax-m2.5-free', 'opencode/nemotron-3-super-free'],
      prepareProvisioning,
      onModelProgress: (progress) => progressUpdates.push(progress),
    });

    await Promise.resolve();
    expect(progressUpdates[0]).toEqual({
      status: 'checking',
      completedCount: 0,
      totalCount: 2,
      details: ['minimax-m2.5-free - checking...', 'nemotron-3-super-free - checking...'],
    });

    deferredCompatibility.resolve({
      ready: true,
      message: 'CLI is ready to launch',
      details: [
        'Selected model opencode/minimax-m2.5-free is compatible. Deep verification pending.',
        'Selected model opencode/nemotron-3-super-free is compatible. Deep verification pending.',
      ],
      warnings: [],
    });

    await vi.waitFor(() =>
      expect(progressUpdates.at(-1)).toEqual({
        status: 'checking',
        completedCount: 0,
        totalCount: 2,
        details: [
          'minimax-m2.5-free - compatible, deep verification pending...',
          'nemotron-3-super-free - compatible, deep verification pending...',
        ],
      })
    );

    deferredDeep.resolve({
      ready: true,
      message: 'CLI is ready to launch',
      details: [
        'Selected model opencode/minimax-m2.5-free verified for launch.',
        'Selected model opencode/nemotron-3-super-free verified for launch.',
      ],
      warnings: [],
    });

    const result = await resultPromise;

    expect(result.status).toBe('ready');
    expect(result.details).toEqual([
      'minimax-m2.5-free - verified',
      'nemotron-3-super-free - verified',
    ]);
    expect(prepareProvisioning).toHaveBeenNthCalledWith(
      1,
      '/tmp/project',
      'opencode',
      ['opencode'],
      ['opencode/minimax-m2.5-free', 'opencode/nemotron-3-super-free'],
      undefined,
      'compatibility'
    );
    expect(prepareProvisioning).toHaveBeenNthCalledWith(
      2,
      '/tmp/project',
      'opencode',
      ['opencode'],
      ['opencode/minimax-m2.5-free', 'opencode/nemotron-3-super-free'],
      undefined,
      'deep'
    );
  });

  it('does not mislabel OpenCode runtime connectivity failures as model unavailable', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message: 'OpenCode: mcp_unavailable',
        details: [
          'OpenCode /experimental/tool/ids unavailable - Unable to connect. Is the computer able to access the url?',
        ],
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      'OpenCode /experimental/tool/ids unavailable - Unable to connect. Is the computer able to access the url?',
      'OpenCode: mcp_unavailable',
    ]);
    expect(result.modelResultsById).toEqual({});
    expect(result.details.join('\n')).not.toContain('big-pickle - unavailable');
    expect(prepareProvisioning).toHaveBeenCalledTimes(1);
    expect(prepareProvisioning).toHaveBeenCalledWith(
      '/tmp/project',
      'opencode',
      ['opencode'],
      ['opencode/big-pickle'],
      undefined,
      'compatibility'
    );
  });

  it('uses structured provider-scoped issues before OpenCode runtime text heuristics', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message: 'OpenCode runtime failed with a future diagnostic shape',
        details: ['Future OpenCode health check failed without known marker words'],
        issues: [
          {
            providerId: 'opencode',
            scope: 'provider',
            severity: 'blocking',
            code: 'future_runtime_failure',
            message: 'Future OpenCode health check failed without known marker words',
          },
        ],
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual(['Future OpenCode health check failed without known marker words']);
    expect(result.modelResultsById).toEqual({});
    expect(result.details.join('\n')).not.toContain('big-pickle - unavailable');
  });

  it('deduplicates repeated OpenCode provider runtime failure details', async () => {
    const runtimeFailure =
      'OpenCode /experimental/tool/ids unavailable - Unable to connect. Is the computer able to access the url?';
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message: runtimeFailure,
        details: [runtimeFailure],
        warnings: [runtimeFailure],
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([runtimeFailure]);
    expect(result.warnings).toEqual([runtimeFailure]);
    expect(result.modelResultsById).toEqual({});
  });

  it('treats OpenCode compatibility verification warnings as blocking when the batch failed', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message:
          'Selected model opencode/big-pickle could not be verified. OpenCode provider authentication failed',
        warnings: [
          'Selected model opencode/big-pickle could not be verified. OpenCode provider authentication failed',
        ],
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      'big-pickle - check failed - OpenCode provider authentication failed',
    ]);
    expect(result.warnings).toEqual([]);
    expect(result.modelResultsById).toEqual({
      'opencode/big-pickle': {
        status: 'failed',
        line: 'big-pickle - check failed - OpenCode provider authentication failed',
        warningLine: null,
      },
    });
  });

  it('treats OpenCode busy model verification as deferred notes', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_cwd, _providerId, _providerIds, _selectedModels, _limitContext, modelVerificationMode) =>
      Promise.resolve(
        modelVerificationMode === 'compatibility'
          ? {
              ready: true,
              message: 'CLI is ready to launch',
              details: [
                'Selected model opencode/big-pickle is compatible. Deep verification pending.',
              ],
              warnings: [],
            }
          : {
              ready: true,
              message: 'CLI is ready to launch',
              warnings: [
                'Selected model opencode/big-pickle verification deferred. OpenCode session is busy; retry when idle.',
              ],
            }
      )
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('notes');
    expect(result.details).toEqual([
      'big-pickle - verification deferred - OpenCode session is busy; retry when idle.',
    ]);
    expect(result.warnings).toEqual([
      'big-pickle - verification deferred - OpenCode session is busy; retry when idle.',
    ]);
    expect(result.modelResultsById).toEqual({
      'opencode/big-pickle': {
        status: 'notes',
        line: 'big-pickle - verification deferred - OpenCode session is busy; retry when idle.',
        warningLine:
          'big-pickle - verification deferred - OpenCode session is busy; retry when idle.',
      },
    });
  });

  it('keeps stale OpenCode model-scoped runtime failures provider-scoped', async () => {
    const runtimeFailure =
      'OpenCode /experimental/tool/ids unavailable - Unable to connect. Is the computer able to access the url?';
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message: `Selected model opencode/big-pickle could not be verified. ${runtimeFailure}`,
        warnings: [`Selected model opencode/big-pickle could not be verified. ${runtimeFailure}`],
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([runtimeFailure]);
    expect(result.warnings).toEqual([]);
    expect(result.modelResultsById).toEqual({});
  });

  it('does not mislabel OpenCode endpoint authorization failures as model unavailable', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message: 'OpenCode: mcp_unavailable',
        details: ['OpenCode /experimental/tool/ids unavailable - HTTP 403 Forbidden'],
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      'OpenCode /experimental/tool/ids unavailable - HTTP 403 Forbidden',
      'OpenCode: mcp_unavailable',
    ]);
    expect(result.modelResultsById).toEqual({});
    expect(result.details.join('\n')).not.toContain('big-pickle - unavailable');
  });

  it('keeps OpenCode selected-model compatibility failures scoped to the selected model', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >(() =>
      Promise.resolve({
        ready: false,
        message:
          'Selected model opencode/not-real is unavailable. Selected model opencode/not-real is not available',
      })
    );

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/not-real'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      'not-real - unavailable - Selected model opencode/not-real is not available',
    ]);
    expect(result.modelResultsById).toEqual({
      'opencode/not-real': {
        status: 'failed',
        line: 'not-real - unavailable - Selected model opencode/not-real is not available',
        warningLine: null,
      },
    });
  });

  it('does not mislabel OpenCode deep runtime failures as model unavailable', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_cwd, _providerId, _providerIds, selectedModels, _limitContext, modelVerificationMode) => {
      if (modelVerificationMode === 'compatibility') {
        expect(selectedModels).toEqual(['opencode/big-pickle']);
        return Promise.resolve({
          ready: true,
          message: 'CLI is ready to launch',
          details: [
            'Selected model opencode/big-pickle is compatible. Deep verification pending.',
          ],
        });
      }

      expect(modelVerificationMode).toBe('deep');
      expect(selectedModels).toEqual(['opencode/big-pickle']);
      return Promise.resolve({
        ready: false,
        message: 'OpenCode: mcp_unavailable',
        details: [
          'OpenCode /experimental/tool/ids unavailable - Unable to connect. Is the computer able to access the url?',
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      'OpenCode /experimental/tool/ids unavailable - Unable to connect. Is the computer able to access the url?',
      'OpenCode: mcp_unavailable',
    ]);
    expect(result.modelResultsById).toEqual({});
    expect(result.details.join('\n')).not.toContain('big-pickle - unavailable');
    expect(prepareProvisioning).toHaveBeenCalledTimes(2);
  });

  it('uses structured provider-scoped issues from OpenCode deep verification', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_cwd, _providerId, _providerIds, selectedModels, _limitContext, modelVerificationMode) => {
      if (modelVerificationMode === 'compatibility') {
        expect(selectedModels).toEqual(['opencode/big-pickle']);
        return Promise.resolve({
          ready: true,
          message: 'CLI is ready to launch',
          details: [
            'Selected model opencode/big-pickle is compatible. Deep verification pending.',
          ],
        });
      }

      expect(modelVerificationMode).toBe('deep');
      expect(selectedModels).toEqual(['opencode/big-pickle']);
      return Promise.resolve({
        ready: false,
        message: 'Future OpenCode runtime health failed',
        details: ['Future OpenCode runtime health failed'],
        issues: [
          {
            providerId: 'opencode',
            scope: 'provider',
            severity: 'blocking',
            code: 'future_runtime_failure',
            message: 'Future OpenCode runtime health failed',
          },
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual(['Future OpenCode runtime health failed']);
    expect(result.modelResultsById).toEqual({});
    expect(result.details.join('\n')).not.toContain('big-pickle - unavailable');
    expect(prepareProvisioning).toHaveBeenCalledTimes(2);
  });

  it('keeps transient OpenCode deep ping failures advisory after compatibility passed', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_cwd, _providerId, _providerIds, selectedModels, _limitContext, modelVerificationMode) => {
      if (modelVerificationMode === 'compatibility') {
        expect(selectedModels).toEqual(['opencode/big-pickle']);
        return Promise.resolve({
          ready: true,
          message: 'CLI is ready to launch',
          details: [
            'Selected model opencode/big-pickle is compatible. Deep verification pending.',
          ],
        });
      }

      expect(modelVerificationMode).toBe('deep');
      expect(selectedModels).toEqual(['opencode/big-pickle']);
      return Promise.resolve({
        ready: false,
        message: 'Unable to connect. Is the computer able to access the url?',
        details: ['Unable to connect. Is the computer able to access the url?'],
        issues: [
          {
            providerId: 'opencode',
            scope: 'provider',
            severity: 'blocking',
            code: 'unknown_error',
            message: 'Unable to connect. Is the computer able to access the url?',
          },
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('notes');
    expect(result.details).toEqual(['big-pickle - ping not confirmed']);
    expect(result.warnings).toEqual([
      'OpenCode model ping was not confirmed. Unable to connect. Is the computer able to access the url?',
      'big-pickle - ping not confirmed',
    ]);
    expect(result.modelResultsById).toEqual({
      'opencode/big-pickle': {
        status: 'notes',
        line: 'big-pickle - ping not confirmed',
        warningLine: 'big-pickle - ping not confirmed',
      },
    });
    expect(prepareProvisioning).toHaveBeenCalledTimes(2);
  });

  it('keeps hard OpenCode deep provider issues blocking after compatibility passed', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_cwd, _providerId, _providerIds, selectedModels, _limitContext, modelVerificationMode) => {
      if (modelVerificationMode === 'compatibility') {
        expect(selectedModels).toEqual(['opencode/big-pickle']);
        return Promise.resolve({
          ready: true,
          message: 'CLI is ready to launch',
          details: [
            'Selected model opencode/big-pickle is compatible. Deep verification pending.',
          ],
        });
      }

      expect(modelVerificationMode).toBe('deep');
      expect(selectedModels).toEqual(['opencode/big-pickle']);
      return Promise.resolve({
        ready: false,
        message: 'OpenCode: mcp_unavailable',
        issues: [
          {
            providerId: 'opencode',
            scope: 'provider',
            severity: 'blocking',
            code: 'mcp_unavailable',
            message: 'OpenCode: mcp_unavailable',
          },
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['opencode/big-pickle'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual(['OpenCode: mcp_unavailable']);
    expect(result.modelResultsById).toEqual({});
    expect(prepareProvisioning).toHaveBeenCalledTimes(2);
  });

  it('keeps OpenCode deep selected-model failures scoped to the selected model', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_cwd, _providerId, _providerIds, selectedModels, _limitContext, modelVerificationMode) => {
      if (modelVerificationMode === 'compatibility') {
        expect(selectedModels).toEqual(['openrouter/example/not-available']);
        return Promise.resolve({
          ready: true,
          message: 'CLI is ready to launch',
          details: [
            'Selected model openrouter/example/not-available is compatible. Deep verification pending.',
          ],
        });
      }

      expect(modelVerificationMode).toBe('deep');
      expect(selectedModels).toEqual(['openrouter/example/not-available']);
      return Promise.resolve({
        ready: false,
        message: 'API Error: 400 {"detail":"The requested model is not available for your account."}',
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['openrouter/example/not-available'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      'example/not-available - unavailable - Not available for this account',
    ]);
    expect(result.modelResultsById).toEqual({
      'openrouter/example/not-available': {
        status: 'failed',
        line: 'example/not-available - unavailable - Not available for this account',
        warningLine: null,
      },
    });
  });

  it('normalizes raw Codex API error envelopes into a clean model reason', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: false,
        message: `API Error: 400 {"type":"error","error":{"type":"api_error","message":"Codex API error (400): {\\"detail\\":\\"The 'gpt-5.1-codex-max' model is not supported when using Codex with a ChatGPT account.\\"}"}}`,
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.1-codex-max'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual([
      '5.1 Codex Max - unavailable - Not available on this Codex native runtime',
    ]);
  });

  it('normalizes raw timeout probe errors into a provider-agnostic reason', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: true,
        message: 'CLI is warmed up and ready to launch',
        warnings: [
          'Selected model gpt-5.3-codex could not be verified. Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.3-codex --max-turns 1 --no-session-persistence',
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.3-codex'],
      prepareProvisioning,
    });

    expect(result.status).toBe('notes');
    expect(result.details).toEqual(['5.3 Codex - check failed - Model verification timed out']);
  });

  it('renders the provider default model as a dedicated Default check line', async () => {
    const progressUpdates: Array<{
      status: 'checking' | 'ready' | 'notes' | 'failed';
      details: string[];
      completedCount: number;
      totalCount: number;
    }> = [];
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: true,
        message: 'CLI is warmed up and ready to launch',
        details: [`Selected model ${DEFAULT_PROVIDER_MODEL_SELECTION} verified for launch.`],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: [DEFAULT_PROVIDER_MODEL_SELECTION],
      prepareProvisioning,
      onModelProgress: (progress) => progressUpdates.push(progress),
    });

    expect(progressUpdates[0]).toEqual({
      status: 'checking',
      completedCount: 0,
      totalCount: 1,
      details: ['Default - checking...'],
    });
    expect(result.status).toBe('ready');
    expect(result.details).toEqual(['Default - verified']);
  });

  it('forwards limitContext through model diagnostics for Anthropic default checks', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: true,
        message: 'CLI is warmed up and ready to launch',
        details: [`Selected model ${DEFAULT_PROVIDER_MODEL_SELECTION} verified for launch.`],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'anthropic',
      selectedModelIds: [DEFAULT_PROVIDER_MODEL_SELECTION],
      limitContext: true,
      prepareProvisioning,
    });

    expect(result.details).toEqual(['Default - verified']);
    expect(prepareProvisioning).toHaveBeenNthCalledWith(
      1,
      '/tmp/project',
      'anthropic',
      ['anthropic'],
      [DEFAULT_PROVIDER_MODEL_SELECTION],
      true,
      'compatibility'
    );
  });

  it('checks multiple Anthropic selected models without OpenCode compatibility-pending progress', async () => {
    const progressUpdates: Array<{
      status: 'checking' | 'ready' | 'notes' | 'failed';
      details: string[];
      completedCount: number;
      totalCount: number;
    }> = [];
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[],
        limitContext?: boolean,
        modelVerificationMode?: 'compatibility' | 'deep'
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels, ____, modelVerificationMode) => {
      if (selectedModels) {
        expect(modelVerificationMode).toBe('compatibility');
        expect(selectedModels).toEqual(['claude-test-a', 'claude-test-b']);
        return Promise.resolve({
          ready: true,
          message: 'CLI is warmed up and ready to launch',
          details: [
            'Selected model claude-test-a verified for launch.',
            'Selected model claude-test-b verified for launch.',
          ],
        });
      }

      expect(modelVerificationMode).toBe('deep');
      return Promise.resolve({
        ready: true,
        message: 'CLI is warmed up and ready to launch',
        details: [],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'anthropic',
      selectedModelIds: ['claude-test-a', 'claude-test-b'],
      prepareProvisioning,
      onModelProgress: (progress) => progressUpdates.push(progress),
    });

    expect(result.status).toBe('ready');
    expect(result.details).toEqual(['claude-test-a - verified', 'claude-test-b - verified']);
    expect(progressUpdates[0]).toEqual({
      status: 'checking',
      completedCount: 0,
      totalCount: 2,
      details: ['claude-test-a - checking...', 'claude-test-b - checking...'],
    });
    expect(
      progressUpdates
        .flatMap((progress) => progress.details)
        .some((line) => line.includes('compatible'))
    ).toBe(false);
    expect(prepareProvisioning).toHaveBeenCalledTimes(2);
    expect(prepareProvisioning).toHaveBeenNthCalledWith(
      1,
      '/tmp/project',
      'anthropic',
      ['anthropic'],
      ['claude-test-a', 'claude-test-b'],
      undefined,
      'compatibility'
    );
    expect(prepareProvisioning).toHaveBeenNthCalledWith(
      2,
      '/tmp/project',
      'anthropic',
      ['anthropic'],
      undefined,
      undefined,
      'deep'
    );
  });

  it('reuses cached model results and probes only newly selected models', async () => {
    const progressUpdates: Array<{
      status: 'checking' | 'ready' | 'notes' | 'failed';
      details: string[];
      completedCount: number;
      totalCount: number;
    }> = [];
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      expect(selectedModels).toEqual(['gpt-5.2-codex']);
      return Promise.resolve({
        ready: false,
        message:
          "Selected model gpt-5.2-codex is unavailable. The 'gpt-5.2-codex' model is not supported when using Codex with a ChatGPT account.",
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.2', 'gpt-5.4-mini', 'gpt-5.2-codex'],
      prepareProvisioning,
      cachedModelResultsById: {
        'gpt-5.2': {
          status: 'ready',
          line: '5.2 - verified',
          warningLine: null,
        },
        'gpt-5.4-mini': {
          status: 'ready',
          line: '5.4 Mini - verified',
          warningLine: null,
        },
      },
      onModelProgress: (progress) => progressUpdates.push(progress),
    });

    expect(progressUpdates[0]).toEqual({
      status: 'checking',
      completedCount: 2,
      totalCount: 3,
      details: ['5.2 - verified', '5.4 Mini - verified', '5.2 Codex - checking...'],
    });
    expect(result.details).toEqual([
      '5.2 - verified',
      '5.4 Mini - verified',
      '5.2 Codex - unavailable - Not available on this Codex native runtime',
    ]);
    expect(prepareProvisioning).toHaveBeenCalledTimes(1);
    expect(prepareProvisioning).toHaveBeenNthCalledWith(
      1,
      '/tmp/project',
      'codex',
      ['codex'],
      ['gpt-5.2-codex'],
      undefined,
      'compatibility'
    );
  });

  it('suppresses a timed out runtime preflight note when that same model later verifies', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: true,
        message: 'CLI is ready to launch (see notes)',
        details: [
          'Selected model gpt-5.4-mini verified for launch.',
          'Selected model gpt-5.4 verified for launch.',
        ],
        warnings: [
          'Preflight check for `orchestrator-cli -p` did not complete. Proceeding anyway. Details: Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.4-mini --max-turns 1 --no-session-persistence',
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.4-mini', 'gpt-5.4'],
      prepareProvisioning,
    });

    expect(result.status).toBe('ready');
    expect(result.warnings).toEqual([]);
    expect(result.details).toEqual(['5.4 Mini - verified', '5.4 - verified']);
  });

  it('treats launchable Codex compatibility as ready and suppresses generic preflight notes', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: true,
        message: 'CLI is ready to launch (see notes)',
        warnings: ['orchestrator-cli preflight check failed (exit code 1).'],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.4'],
      prepareProvisioning,
    });

    expect(result.status).toBe('ready');
    expect(result.warnings).toEqual([]);
    expect(result.details).toEqual(['5.4 - available for launch']);
    expect(result.modelResultsById).toEqual({
      'gpt-5.4': {
        status: 'ready',
        line: '5.4 - available for launch',
        warningLine: null,
      },
    });
  });

  it('suppresses a generic runtime preflight failure when selected models later verify', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: true,
        message: 'CLI is ready to launch (see notes)',
        details: ['Selected model gpt-5.4 verified for launch.'],
        warnings: [
          'orchestrator-cli preflight check failed (exit code 1). Details: upstream unavailable',
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: ['gpt-5.4'],
      prepareProvisioning,
    });

    expect(result.status).toBe('ready');
    expect(result.warnings).toEqual([]);
    expect(result.details).toEqual(['5.4 - verified']);
    expect(result.modelResultsById).toEqual({
      'gpt-5.4': {
        status: 'ready',
        line: '5.4 - verified',
        warningLine: null,
      },
    });
  });

  it('suppresses a generic runtime preflight note during progress when cached selected models are already verified', async () => {
    const progressUpdates: Array<{
      status: 'checking' | 'ready' | 'notes' | 'failed';
      details: string[];
      completedCount: number;
      totalCount: number;
    }> = [];
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      if (!selectedModels || selectedModels.length === 0) {
        return Promise.resolve({
          ready: true,
          message: 'CLI is ready to launch (see notes)',
          warnings: ['orchestrator-cli preflight check failed (exit code 1).'],
        });
      }

      return Promise.resolve({
        ready: true,
        message: 'CLI is ready to launch (see notes)',
        warnings: ['orchestrator-cli preflight check failed (exit code 1).'],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'codex',
      selectedModelIds: [DEFAULT_PROVIDER_MODEL_SELECTION, 'gpt-5.4'],
      prepareProvisioning,
      onModelProgress: (progress) => progressUpdates.push(progress),
      cachedModelResultsById: {
        [DEFAULT_PROVIDER_MODEL_SELECTION]: {
          status: 'ready',
          line: 'Default - verified',
          warningLine: null,
        },
        'gpt-5.4': {
          status: 'ready',
          line: '5.4 - verified',
          warningLine: null,
        },
      },
    });

    expect(prepareProvisioning).toHaveBeenCalledTimes(1);
    expect(progressUpdates).toEqual([
      {
        status: 'ready',
        completedCount: 2,
        totalCount: 2,
        details: ['Default - verified', '5.4 - verified'],
      },
    ]);
    expect(result.status).toBe('ready');
    expect(result.warnings).toEqual([]);
    expect(result.details).toEqual(['Default - verified', '5.4 - verified']);
  });

  it('uses structured OpenCode auth diagnostics as provider-scoped failures', async () => {
    const prepareProvisioning = vi.fn<
      (
        cwd?: string,
        providerId?: TeamProviderId,
        providerIds?: TeamProviderId[],
        selectedModels?: string[]
      ) => Promise<TeamProvisioningPrepareResult>
    >((_, __, ___, selectedModels) => {
      return Promise.resolve({
        ready: false,
        message: 'OpenCode: not_authenticated',
        details: ['Token refresh failed: 401'],
        issues: [
          {
            providerId: 'opencode',
            scope: 'provider',
            severity: 'blocking',
            code: 'not_authenticated',
            message: 'Token refresh failed: 401',
          },
        ],
      });
    });

    const result = await runProviderPrepareDiagnostics({
      cwd: '/tmp/project',
      providerId: 'opencode',
      selectedModelIds: ['openai/gpt-5.2-codex'],
      prepareProvisioning,
    });

    expect(result.status).toBe('failed');
    expect(result.details).toEqual(['Token refresh failed: 401']);
    expect(result.modelResultsById).toEqual({});
  });
});
