import { describe, it, expect, beforeEach } from 'vitest';
import { LadderEngine } from './engine';
import type { Program, Rung } from './types';
import { MathOperationError, InvalidMemoryAccessError } from './errors';

describe('Hardening: Math and Error Handling', () => {
  let engine: LadderEngine;

  beforeEach(() => {
    engine = new LadderEngine();
  });

  describe('Math Operations', () => {
    it('performs ADD operation', () => {
      const rung: Rung = {
        id: 'r1',
        enabled: true,
        series: [
          { type: 'math', operator: 'ADD', inputA: 'I:1', inputB: 'I:2', outputAddress: 'M:1' },
          { type: 'coil', coilType: 'OUTPUT', address: 'Q:1' }
        ]
      };
      engine.loadProgram({ name: 'math-test', rungs: [rung], cycleTime: 100 });
      engine.setInput('I:1', 10.5);
      engine.setInput('I:2', 5.5);
      engine.scan();
      expect(engine.getMemory('M:1')).toBe(16);
    });

    it('performs SUB operation', () => {
      const rung: Rung = {
        id: 'r1',
        enabled: true,
        series: [
          { type: 'math', operator: 'SUB', inputA: 'I:1', inputB: 'I:2', outputAddress: 'M:1' },
          { type: 'coil', coilType: 'OUTPUT', address: 'Q:1' }
        ]
      };
      engine.loadProgram({ name: 'math-test', rungs: [rung], cycleTime: 100 });
      engine.setInput('I:1', 10);
      engine.setInput('I:2', 4.5);
      engine.scan();
      expect(engine.getMemory('M:1')).toBe(5.5);
    });

    it('performs MUL operation', () => {
      const rung: Rung = {
        id: 'r1',
        enabled: true,
        series: [
          { type: 'math', operator: 'MUL', inputA: 'I:1', inputB: 'I:2', outputAddress: 'M:1' },
          { type: 'coil', coilType: 'OUTPUT', address: 'Q:1' }
        ]
      };
      engine.loadProgram({ name: 'math-test', rungs: [rung], cycleTime: 100 });
      engine.setInput('I:1', 3);
      engine.setInput('I:2', 2.5);
      engine.scan();
      expect(engine.getMemory('M:1')).toBe(7.5);
    });

    it('performs DIV operation', () => {
      const rung: Rung = {
        id: 'r1',
        enabled: true,
        series: [
          { type: 'math', operator: 'DIV', inputA: 'I:1', inputB: 'I:2', outputAddress: 'M:1' },
          { type: 'coil', coilType: 'OUTPUT', address: 'Q:1' }
        ]
      };
      engine.loadProgram({ name: 'math-test', rungs: [rung], cycleTime: 100 });
      engine.setInput('I:1', 10);
      engine.setInput('I:2', 4);
      engine.scan();
      expect(engine.getMemory('M:1')).toBe(2.5);
    });

    it('throws error on division by zero', () => {
      const rung: Rung = {
        id: 'r1',
        enabled: true,
        series: [
          { type: 'math', operator: 'DIV', inputA: 'I:1', inputB: 'I:2', outputAddress: 'M:1' },
          { type: 'coil', coilType: 'OUTPUT', address: 'Q:1' }
        ]
      };
      engine.loadProgram({ name: 'math-test', rungs: [rung], cycleTime: 100 });
      engine.setInput('I:1', 10);
      engine.setInput('I:2', 0);
      
      // Since errors are caught in evaluateSeries and logged to console, 
      // we might not see them thrown here directly if we just call scan().
      // But if we want to test the logic, we should check how the engine handles it.
      // The engine catches it and sets result to false.
      engine.scan();
      expect(engine.getOutput('Q:1')).toBe(false);
    });
  });

  describe('Error Handling', () => {
    it('handles invalid memory access gracefully', () => {
      const rung: Rung = {
        id: 'r1',
        enabled: true,
        series: [
          { type: 'contact', contactType: 'NO', address: 'INVALID_ADDR' },
          { type: 'coil', coilType: 'OUTPUT', address: 'Q:1' }
        ]
      };
      engine.loadProgram({ name: 'error-test', rungs: [rung], cycleTime: 100 });
      engine.scan();
      // Should not crash, and because contact access failed, Q:1 should be false
      expect(engine.getOutput('Q:1')).toBe(false);
    });
  });

  describe('State Snapshots', () => {
    it('can take and restore a snapshot', () => {
      engine.setInput('I:0.0', true);
      engine.setMemory('M:0.0', true);
      
      const snapshot = engine.takeSnapshot();
      
      // Modify state
      engine.setInput('I:0.0', false);
      engine.setMemory('M:0.0', false);
      expect(engine.getInput('I:0.0')).toBe(false);
      expect(engine.getMemory('M:0.0')).toBe(false);
      
      // Restore
      engine.restoreSnapshot(snapshot);
      expect(engine.getInput('I:0.0')).toBe(true);
      expect(engine.getMemory('M:0.0')).toBe(true);
    });

    it('ensures snapshot is a deep copy', () => {
      engine.setInput('I:0.0', true);
      const snapshot = engine.takeSnapshot();
      
      engine.setInput('I:0.0', false);
      
      expect(snapshot.inputs.get('I:0.0')?.value).toBe(true);
      expect(engine.getInput('I:0.0')).toBe(false);
    });
  });
});