// @lifecycle canonical - Unit tests for InjectionDecisionService
import { beforeEach, describe, expect, it, jest } from '@jest/globals';

import { DEFAULT_INJECTION_CONFIG } from '../../../../src/engine/execution/pipeline/decisions/injection/constants.js';
import { InjectionDecisionService } from '../../../../src/engine/execution/pipeline/decisions/injection/injection-decision-service.js';

import type {
  InjectionConfig,
  InjectionDecisionInput,
  InjectionRuntimeOverride,
  InjectionType,
} from '../../../../src/engine/execution/pipeline/decisions/injection/types.js';

describe('InjectionDecisionService', () => {
  let mockLogger: any;

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

  describe('constructor', () => {
    it('should create service with default config', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      expect(service).toBeDefined();
    });

    it('should create service with custom config', () => {
      const customConfig: InjectionConfig = {
        ...DEFAULT_INJECTION_CONFIG,
        defaults: {
          'system-prompt': false,
          'gate-guidance': true,
          'style-guidance': true,
        },
      };
      const service = new InjectionDecisionService(customConfig, mockLogger);
      expect(service).toBeDefined();
    });
  });

  describe('decide', () => {
    it('should return inject=true for system-prompt with default config on step 1', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'system-prompt',
        currentStep: 1,
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(true);
      // DEFAULT_INJECTION_CONFIG has global config, so source is 'global-config'
      expect(decision.source).toBe('global-config');
    });

    it('should return inject=false for system-prompt with %clean modifier', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'system-prompt',
        currentStep: 1,
        modifiers: { clean: true },
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(false);
      expect(decision.source).toBe('modifier');
      expect(decision.reason).toContain('clean');
    });

    it('should return inject=false for style-guidance with %lean modifier', () => {
      // %lean disables system-prompt and style-guidance, but NOT gate-guidance
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'style-guidance',
        currentStep: 1,
        modifiers: { lean: true },
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(false);
      expect(decision.source).toBe('modifier');
    });

    it('should NOT disable gate-guidance with %lean modifier', () => {
      // %lean only affects system-prompt and style-guidance
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'gate-guidance',
        currentStep: 1,
        modifiers: { lean: true },
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(true);
      expect(decision.source).not.toBe('modifier');
    });

    it('should honor runtime override set via setRuntimeOverride', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);

      // Set runtime override via the service's method
      service.setRuntimeOverride({
        type: 'system-prompt',
        enabled: false,
        scope: 'session',
        setAt: Date.now(),
      });

      const input: InjectionDecisionInput = {
        injectionType: 'system-prompt',
        currentStep: 1,
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(false);
      expect(decision.source).toBe('runtime-override');
    });

    it('should honor overrides synced from session manager and clear cached decisions', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);

      // Prime cache with default decision (inject=true)
      service.decide({
        injectionType: 'system-prompt',
        currentStep: 1,
      });

      const overrides = new Map<InjectionType, InjectionRuntimeOverride>();
      overrides.set('system-prompt', {
        type: 'system-prompt',
        enabled: false,
        scope: 'session',
        setAt: Date.now(),
      });

      service.syncRuntimeOverrides(overrides);

      const decision = service.decide({
        injectionType: 'system-prompt',
        currentStep: 1,
      });

      expect(decision.inject).toBe(false);
      expect(decision.source).toBe('runtime-override');
    });

    it('should cache decisions for same input', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'system-prompt',
        currentStep: 1,
      };

      const decision1 = service.decide(input);
      const decision2 = service.decide(input);

      // Both should have same decidedAt (cached)
      expect(decision1.decidedAt).toBe(decision2.decidedAt);
    });
  });

  describe('decideAll', () => {
    it('should return decisions for all injection types', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input = {
        currentStep: 1,
      };

      const state = service.decideAll(input);

      expect(state.systemPrompt).toBeDefined();
      expect(state.gateGuidance).toBeDefined();
      expect(state.styleGuidance).toBeDefined();
    });

    it('should track currentStep in state', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input = {
        currentStep: 3,
      };

      const state = service.decideAll(input);

      expect(state.currentStep).toBe(3);
    });
  });

  describe('frequency handling', () => {
    it('should inject on first step with first-only frequency', () => {
      const config: InjectionConfig = {
        ...DEFAULT_INJECTION_CONFIG,
        'system-prompt': {
          enabled: true,
          frequency: { mode: 'first-only' },
        },
      };
      const service = new InjectionDecisionService(config, mockLogger);

      const decision = service.decide({
        injectionType: 'system-prompt',
        currentStep: 1,
      });

      expect(decision.inject).toBe(true);
    });

    it('should not inject on subsequent steps with first-only frequency', () => {
      const config: InjectionConfig = {
        ...DEFAULT_INJECTION_CONFIG,
        'system-prompt': {
          enabled: true,
          frequency: { mode: 'first-only' },
        },
      };
      const service = new InjectionDecisionService(config, mockLogger);

      const decision = service.decide({
        injectionType: 'system-prompt',
        currentStep: 2,
      });

      expect(decision.inject).toBe(false);
      expect(decision.reason).toContain('first-only');
    });

    it('should never inject with never frequency', () => {
      const config: InjectionConfig = {
        ...DEFAULT_INJECTION_CONFIG,
        'system-prompt': {
          enabled: true,
          frequency: { mode: 'never' },
        },
      };
      const service = new InjectionDecisionService(config, mockLogger);

      const decision1 = service.decide({
        injectionType: 'system-prompt',
        currentStep: 1,
      });
      const decision2 = service.decide({
        injectionType: 'system-prompt',
        currentStep: 5,
      });

      expect(decision1.inject).toBe(false);
      expect(decision2.inject).toBe(false);
    });

    it('should inject at intervals with every frequency', () => {
      const config: InjectionConfig = {
        ...DEFAULT_INJECTION_CONFIG,
        'system-prompt': {
          enabled: true,
          frequency: { mode: 'every', interval: 2 },
        },
      };

      // Test each step with fresh service to avoid caching issues
      const service1 = new InjectionDecisionService(config, mockLogger);
      const decision1 = service1.decide({
        injectionType: 'system-prompt',
        currentStep: 1,
      });
      expect(decision1.inject).toBe(true); // Step 1 always injects

      const service2 = new InjectionDecisionService(config, mockLogger);
      const decision2 = service2.decide({
        injectionType: 'system-prompt',
        currentStep: 2,
      });
      expect(decision2.inject).toBe(false); // Step 2: (2-1) % 2 = 1, not 0

      const service3 = new InjectionDecisionService(config, mockLogger);
      const decision3 = service3.decide({
        injectionType: 'system-prompt',
        currentStep: 3,
      });
      expect(decision3.inject).toBe(true); // Step 3: (3-1) % 2 = 0
    });
  });

  describe('modifier precedence', () => {
    it('should prioritize %clean over runtime override', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'system-prompt',
        currentStep: 1,
        modifiers: { clean: true },
        sessionOverrides: { 'system-prompt': true },
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(false);
      expect(decision.source).toBe('modifier');
    });

    it('should allow injection with %judge modifier', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);
      const input: InjectionDecisionInput = {
        injectionType: 'system-prompt',
        currentStep: 1,
        modifiers: { judge: true },
      };

      const decision = service.decide(input);

      expect(decision.inject).toBe(true);
      expect(decision.source).toBe('modifier');
    });
  });

  describe('cache management', () => {
    it('should reset cached decisions', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);

      // Make a decision
      service.decide({
        injectionType: 'system-prompt',
        currentStep: 1,
      });

      expect(service.hasDecided('system-prompt')).toBe(true);

      // Reset cache
      service.reset();

      expect(service.hasDecided('system-prompt')).toBe(false);
    });

    it('should cache decisions by type', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);

      // Make decisions
      service.decide({ injectionType: 'system-prompt', currentStep: 1 });
      service.decide({ injectionType: 'gate-guidance', currentStep: 1 });

      expect(service.hasDecided('system-prompt')).toBe(true);
      expect(service.hasDecided('gate-guidance')).toBe(true);
      expect(service.hasDecided('style-guidance')).toBe(false);
    });

    it('should return cached decision without recomputing', () => {
      const service = new InjectionDecisionService(DEFAULT_INJECTION_CONFIG, mockLogger);

      // Make a decision
      service.decide({ injectionType: 'system-prompt', currentStep: 1 });

      // Get cached
      const cached = service.getCachedDecision('system-prompt');

      expect(cached).toBeDefined();
      expect(cached?.inject).toBe(true);
    });
  });
});
