import { describe, expect, expectTypeOf, it } from 'vitest';
import { HARNESS_CAPABILITIES, HARNESS_ERROR_CODES } from './index.js';
import type {
  AttachConversation,
  ConversationSnapshot,
  CreateHarnessSession,
  HarnessAdapter,
  HarnessCapability,
  HarnessConversationService,
  HarnessDescriptor,
  HarnessError,
  HarnessErrorCode,
  HarnessEvent,
  HarnessEventEnvelope,
  HarnessInteractionState,
  HarnessPrompt,
  HarnessPromptReceipt,
  HarnessSelection,
  HarnessSessionHandle,
  HarnessSessionSnapshot,
  ResumeHarnessSession,
  SendHarnessTurn,
  TurnReceipt,
} from '../index.js';

const EXPECTED_CAPABILITIES = [
  'modelSelection',
  'thinkingLevels',
  'images',
  'toolEvents',
  'extensionUi',
  'steering',
  'followUp',
  'compaction',
  'persistentResume',
] as const satisfies readonly HarnessCapability[];

const EXPECTED_ERROR_CODES = [
  'auth_required',
  'selection_invalid',
  'catalog_unavailable',
  'catalog_stale',
  'model_unavailable',
  'no_viable_provider',
  'session_create_failed',
  'session_not_found',
  'resume_conflict',
  'session_busy',
  'auth_bundle_concurrency_unverified',
  'adapter_unavailable',
  'sandbox_unavailable',
  'rpc_version_unsupported',
  'rpc_protocol_error',
  'process_exited',
  'outcome_unknown',
  'interaction_unsupported',
  'aborted',
] as const satisfies readonly HarnessErrorCode[];

function assertNever(value: never): never {
  throw new Error(`Unexpected contract variant: ${JSON.stringify(value)}`);
}

function describeEvent(event: HarnessEvent): string {
  switch (event.type) {
    case 'session.started':
    case 'session.state':
    case 'session.identity_changed':
    case 'turn.started':
    case 'text.delta':
    case 'thinking.delta':
    case 'tool.started':
    case 'tool.updated':
    case 'tool.finished':
    case 'interaction.required':
    case 'usage.updated':
    case 'turn.completed':
    case 'error':
      return event.type;
    default:
      return assertNever(event);
  }
}

function describeError(error: HarnessError): HarnessErrorCode {
  switch (error.code) {
    case 'auth_required':
    case 'selection_invalid':
    case 'catalog_unavailable':
    case 'catalog_stale':
    case 'model_unavailable':
    case 'no_viable_provider':
    case 'session_create_failed':
    case 'session_not_found':
    case 'resume_conflict':
    case 'session_busy':
    case 'auth_bundle_concurrency_unverified':
    case 'adapter_unavailable':
    case 'sandbox_unavailable':
    case 'rpc_version_unsupported':
    case 'rpc_protocol_error':
    case 'process_exited':
    case 'outcome_unknown':
    case 'interaction_unsupported':
    case 'aborted':
      return error.code;
    default:
      return assertNever(error);
  }
}

describe('generic harness contracts', (): void => {
  it('keeps harness, provider, model, conversation, native session, process, and seat separate', (): void => {
    const selection = {
      harnessId: 'pi',
      providerId: 'openai-codex',
      modelId: 'gpt-5-codex',
    } satisfies HarnessSelection;
    const snapshot = {
      conversationId: 'conversation-1',
      nativeSessionId: 'native-session-1',
      processId: 'process-1',
      seatId: 'seat-1',
      selection,
      state: 'idle',
      attachedClientIds: ['browser-1'],
    } satisfies HarnessSessionSnapshot;

    const identifiers = [
      snapshot.selection.harnessId,
      snapshot.selection.providerId,
      snapshot.selection.modelId,
      snapshot.conversationId,
      snapshot.nativeSessionId,
      snapshot.processId,
      snapshot.seatId,
    ];

    expect(new Set(identifiers).size).toBe(7);
    expect(snapshot).toMatchObject({
      conversationId: 'conversation-1',
      nativeSessionId: 'native-session-1',
      processId: 'process-1',
      seatId: 'seat-1',
      selection,
    });
  });

  it('advertises the complete capability set as checked literals', (): void => {
    const descriptor = {
      id: 'pi',
      displayName: 'Pi',
      capabilities: HARNESS_CAPABILITIES,
    } satisfies HarnessDescriptor;

    expect(HARNESS_CAPABILITIES).toEqual(EXPECTED_CAPABILITIES);
    expect(descriptor.capabilities).toEqual(EXPECTED_CAPABILITIES);
  });

  it('exposes every stable error code as an exhaustive discriminated union', (): void => {
    const selection: HarnessSelection = {
      harnessId: 'pi',
      providerId: 'openai-codex',
      modelId: 'gpt-5-codex',
    };
    const error: HarnessError = {
      code: 'model_unavailable',
      message: 'The selected model is unavailable.',
      retryable: true,
      correlationId: 'correlation-1',
      selection,
    };

    expect(HARNESS_ERROR_CODES).toEqual(EXPECTED_ERROR_CODES);
    expect(describeError(error)).toBe('model_unavailable');
    const receipt = {
      conversationId: 'conversation-1',
      turnId: 'turn-1',
      correlationId: 'correlation-1',
      state: 'accepted',
      selection,
    } satisfies HarnessPromptReceipt;

    expect(error.selection).toEqual(selection);
    expect(receipt.selection).toEqual(selection);
    expect('effectiveSelection' in error).toBe(false);
    expect('effectiveSelection' in receipt).toBe(false);
  });

  it('wraps every normalized event variant in the persisted envelope', (): void => {
    const selection: HarnessSelection = {
      harnessId: 'pi',
      providerId: 'openai-codex',
      modelId: 'gpt-5-codex',
    };
    const toolStarted: HarnessEvent = {
      type: 'tool.started',
      toolCallId: 'tool-call-1',
      toolName: 'read',
    };
    const events: readonly HarnessEvent[] = [
      { type: 'session.started', state: 'idle' },
      { type: 'session.state', state: 'busy' },
      {
        type: 'session.identity_changed',
        identityGeneration: 2,
        label: 'Re-enrolled account',
      },
      { type: 'turn.started' },
      { type: 'text.delta', text: 'Hello' },
      { type: 'thinking.delta', text: 'Reasoning' },
      toolStarted,
      {
        type: 'tool.updated',
        toolCallId: 'tool-call-1',
        toolName: 'read',
        message: 'Reading',
      },
      {
        type: 'tool.finished',
        toolCallId: 'tool-call-1',
        toolName: 'read',
        isError: false,
      },
      {
        type: 'interaction.required',
        requestId: 'interaction-1',
        interactionType: 'confirm',
        state: 'pending',
        prompt: 'Continue?',
      },
      {
        type: 'usage.updated',
        usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
      },
      { type: 'turn.completed', outcome: 'settled' },
      {
        type: 'error',
        error: {
          code: 'rpc_protocol_error',
          message: 'The harness protocol failed.',
          retryable: false,
          correlationId: 'correlation-1',
          selection,
        },
      },
    ];
    const envelope: HarnessEventEnvelope = {
      conversationId: 'conversation-1',
      nativeSessionId: 'native-session-1',
      turnId: 'turn-1',
      correlationId: 'correlation-1',
      sequence: 42,
      nativeEntryCursor: 'native-entry-7',
      occurredAt: '2026-08-11T12:00:00.000Z',
      harnessId: 'pi',
      selection,
      event: toolStarted,
    };

    expect(events.map(describeEvent)).toEqual([
      'session.started',
      'session.state',
      'session.identity_changed',
      'turn.started',
      'text.delta',
      'thinking.delta',
      'tool.started',
      'tool.updated',
      'tool.finished',
      'interaction.required',
      'usage.updated',
      'turn.completed',
      'error',
    ]);
    expect(envelope).toMatchObject({
      conversationId: 'conversation-1',
      nativeSessionId: 'native-session-1',
      turnId: 'turn-1',
      correlationId: 'correlation-1',
      sequence: 42,
      nativeEntryCursor: 'native-entry-7',
      harnessId: 'pi',
      selection,
      event: toolStarted,
    });
  });

  it('models the complete one-response interaction lifecycle', (): void => {
    const states = [
      'pending',
      'responded',
      'cancelled',
      'expired',
    ] as const satisfies readonly HarnessInteractionState[];

    expect(states).toEqual(['pending', 'responded', 'cancelled', 'expired']);
  });

  it('preserves the approved adapter and conversation method signatures', (): void => {
    expectTypeOf<HarnessAdapter['create']>().toEqualTypeOf<
      (input: CreateHarnessSession) => Promise<HarnessSessionHandle>
    >();
    expectTypeOf<HarnessAdapter['resume']>().toEqualTypeOf<
      (input: ResumeHarnessSession) => Promise<HarnessSessionHandle>
    >();
    expectTypeOf<HarnessSessionHandle['prompt']>().toEqualTypeOf<
      (input: HarnessPrompt & { idempotencyKey: string }) => Promise<HarnessPromptReceipt>
    >();
    expectTypeOf<HarnessConversationService['attach']>().toEqualTypeOf<
      (input: AttachConversation & { afterSequence?: number }) => Promise<ConversationSnapshot>
    >();
    expectTypeOf<HarnessConversationService['send']>().toEqualTypeOf<
      (input: SendHarnessTurn & { idempotencyKey: string }) => Promise<TurnReceipt>
    >();
  });
});
