import { describe, expect, jest, test } from '@jest/globals';

import { ExecutionContext } from '../../../src/engine/execution/context/execution-context.js';
import { FrameworkDecisionAuthority } from '../../../src/engine/execution/pipeline/decisions/index.js';
import { DiagnosticAccumulator } from '../../../src/engine/execution/pipeline/state/accumulators/diagnostic-accumulator.js';
import { GateAccumulator } from '../../../src/engine/execution/pipeline/state/accumulators/gate-accumulator.js';

import type { ChainStepPrompt } from '../../../src/engine/execution/operators/chain-operator-executor.js';
import type { ConvertedPrompt } from '../../../src/shared/types/index.js';

const baseRequest = { command: '>>demo' };

const mockLogger = {
  debug: jest.fn(),
  info: jest.fn(),
  warn: jest.fn(),
  error: jest.fn(),
};

const samplePrompt: ConvertedPrompt = {
  id: 'demo',
  name: 'Demo Prompt',
  description: 'Used for execution-context tests',
  category: 'test',
  userMessageTemplate: 'Hello {{name}}',
  arguments: [],
};

describe('ExecutionContext helpers', () => {
  test('getSessionId prefers resume metadata value', () => {
    const context = new ExecutionContext({ ...baseRequest, chain_id: 'chain-demo' });
    context.state.session.resumeSessionId = 'metadata-session';
    context.sessionContext = { sessionId: 'context-session', isChainExecution: false } as any;
    expect(context.getSessionId()).toBe('metadata-session');
  });

  test('getSessionId falls back to session context', () => {
    const context = new ExecutionContext(baseRequest);
    context.sessionContext = { sessionId: 'context-session', isChainExecution: true };
    expect(context.getSessionId()).toBe('context-session');
  });

  test('isChainExecution inspects execution plan and parsed command', () => {
    const context = new ExecutionContext(baseRequest);
    expect(context.isChainExecution()).toBe(false);

    const chainResumeContext = new ExecutionContext({ chain_id: 'chain-demo' });
    expect(chainResumeContext.isChainExecution()).toBe(true);

    context.executionPlan = {
      strategy: 'chain',
      gates: [],
      requiresFramework: false,
      requiresSession: true,
    };
    expect(context.isChainExecution()).toBe(true);

    const chainContext = new ExecutionContext(baseRequest);
    chainContext.parsedCommand = {
      promptId: 'chain',
      rawArgs: '',
      format: 'symbolic',
      confidence: 0.8,
      metadata: {
        originalCommand: '>>chain',
        parseStrategy: 'symbolic',
        detectedFormat: 'symbolic',
        warnings: [],
      },
      commandType: 'chain',
      steps: [{ promptId: 'demo', stepNumber: 1, args: {} }] as ChainStepPrompt[],
    };
    expect(chainContext.isChainExecution()).toBe(true);
  });

  test('hasSinglePromptCommand type guard', () => {
    const context = new ExecutionContext(baseRequest);
    context.parsedCommand = {
      promptId: 'demo',
      rawArgs: '',
      format: 'simple',
      confidence: 0.9,
      metadata: {
        originalCommand: '>>demo',
        parseStrategy: 'simple',
        detectedFormat: 'simple',
        warnings: [],
      },
      commandType: 'single',
      convertedPrompt: samplePrompt,
    };

    expect(context.hasSinglePromptCommand()).toBe(true);
    if (context.hasSinglePromptCommand()) {
      expect(context.parsedCommand.convertedPrompt.id).toBe('demo');
    }
  });

  test('hasChainCommand type guard', () => {
    const context = new ExecutionContext(baseRequest);
    context.parsedCommand = {
      promptId: 'demo',
      rawArgs: '',
      format: 'symbolic',
      confidence: 0.9,
      metadata: {
        originalCommand: '>>demo',
        parseStrategy: 'symbolic',
        detectedFormat: 'symbolic',
        warnings: [],
      },
      commandType: 'chain',
      steps: [{ promptId: 'demo', stepNumber: 1, args: {} }] as ChainStepPrompt[],
    };

    expect(context.hasChainCommand()).toBe(true);
    if (context.hasChainCommand()) {
      expect(context.parsedCommand.steps.length).toBe(1);
    }
  });
});

describe('ExecutionContext pipeline state management', () => {
  test('initializes GateAccumulator', () => {
    const context = new ExecutionContext(baseRequest);
    expect(context.gates).toBeInstanceOf(GateAccumulator);
    expect(context.gates.size).toBe(0);
  });

  test('initializes DiagnosticAccumulator', () => {
    const context = new ExecutionContext(baseRequest);
    expect(context.diagnostics).toBeInstanceOf(DiagnosticAccumulator);
    expect(context.diagnostics.size).toBe(0);
  });

  test('initializes FrameworkDecisionAuthority', () => {
    const context = new ExecutionContext(baseRequest);
    expect(context.frameworkAuthority).toBeInstanceOf(FrameworkDecisionAuthority);
    expect(context.frameworkAuthority.hasDecided()).toBe(false);
  });

  test('accepts optional logger for state management', () => {
    const context = new ExecutionContext(baseRequest, mockLogger as any);

    // Add a gate to trigger logging
    context.gates.add('test-gate', 'registry-auto');

    // The logger should have been called
    expect(context.gates.has('test-gate')).toBe(true);
  });

  test('gates accumulator deduplicates with priority', () => {
    const context = new ExecutionContext(baseRequest);

    // Add from low priority source
    context.gates.add('test-gate', 'registry-auto'); // priority 20

    // Try to add from same/lower priority - should be rejected
    const added = context.gates.add('test-gate', 'registry-auto');
    expect(added).toBe(false);
    expect(context.gates.size).toBe(1);

    // Add from higher priority source - should override
    const overridden = context.gates.add('test-gate', 'inline-operator'); // priority 100
    expect(overridden).toBe(true);
    expect(context.gates.size).toBe(1);

    const entries = context.gates.getEntries();
    expect(entries[0].source).toBe('inline-operator');
  });

  test('diagnostics accumulator collects across stages', () => {
    const context = new ExecutionContext(baseRequest);

    context.diagnostics.info('Stage1', 'Info message');
    context.diagnostics.warn('Stage2', 'Warning message');
    context.diagnostics.error('Stage3', 'Error message', 'ERR_CODE');

    expect(context.diagnostics.size).toBe(3);
    expect(context.diagnostics.hasErrors()).toBe(true);
    expect(context.diagnostics.hasWarnings()).toBe(true);

    const summary = context.diagnostics.getSummary();
    expect(summary.info).toBe(1);
    expect(summary.warning).toBe(1);
    expect(summary.error).toBe(1);
  });

  test('framework authority caches decision', () => {
    const context = new ExecutionContext(baseRequest);

    // Set up execution plan with modifiers
    context.executionPlan = {
      strategy: 'single',
      gates: [],
      requiresFramework: true,
      requiresSession: false,
      modifiers: { clean: true },
    };

    // Make decision
    const decision = context.frameworkAuthority.decide({
      modifiers: context.executionPlan.modifiers,
      globalActiveFramework: 'CAGEERF',
    });

    expect(decision.shouldApply).toBe(false);
    expect(decision.source).toBe('disabled');
    expect(decision.reason).toContain('%clean');

    // Verify decision is cached (same object reference)
    const cachedDecision = context.frameworkAuthority.decide({
      globalActiveFramework: 'CAGEERF',
    });
    expect(cachedDecision).toBe(decision);
  });

  test('framework authority respects priority chain', () => {
    const context = new ExecutionContext(baseRequest);

    // Test operator override takes precedence
    const decision = context.frameworkAuthority.decide({
      operatorOverride: 'CAGEERF',
      clientOverride: 'ReACT',
      globalActiveFramework: '5W1H',
    });

    expect(decision.shouldApply).toBe(true);
    expect(decision.frameworkId).toBe('cageerf');
    expect(decision.source).toBe('operator');
  });

  test('state management objects are readonly', () => {
    const context = new ExecutionContext(baseRequest);

    // These should be readonly - TypeScript would catch reassignment
    // Runtime check that they exist
    expect(context.gates).toBeDefined();
    expect(context.diagnostics).toBeDefined();
    expect(context.frameworkAuthority).toBeDefined();
  });
});
