/**
 * Test Intelligence Plugin - Bridge Tests
 *
 * Tests for TestLearningBridge initialization, lifecycle, and methods
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { TestLearningBridge } from '../src/bridges/learning-bridge.js';

// Mock WASM module
vi.mock('../src/bridges/learning-wasm.js', () => ({
  initWasm: vi.fn().mockResolvedValue(undefined),
  wasmAvailable: vi.fn().mockReturnValue(false),
}));

describe('TestLearningBridge', () => {
  let bridge: TestLearningBridge;

  beforeEach(() => {
    vi.clearAllMocks();
    bridge = new TestLearningBridge();
  });

  afterEach(async () => {
    if (bridge.isReady()) {
      await bridge.destroy();
    }
  });

  describe('Initialization', () => {
    it('should create bridge instance', () => {
      expect(bridge).toBeInstanceOf(TestLearningBridge);
    });

    it('should not be ready before init', () => {
      expect(bridge.isReady()).toBe(false);
    });

    it('should initialize successfully', async () => {
      await bridge.init();
      expect(bridge.isReady()).toBe(true);
    });

    it('should initialize with custom config', async () => {
      await bridge.init({
        algorithm: 'ppo',
        learningRate: 0.001,
        gamma: 0.95,
        epsilon: 0.2,
      });
      expect(bridge.isReady()).toBe(true);
    });

    it('should initialize with q-learning algorithm', async () => {
      await bridge.init({
        algorithm: 'q-learning',
        learningRate: 0.1,
        gamma: 0.99,
      });
      expect(bridge.isReady()).toBe(true);
    });

    it('should initialize with decision-transformer algorithm', async () => {
      await bridge.init({
        algorithm: 'decision-transformer',
        contextLength: 20,
        embeddingDim: 128,
      });
      expect(bridge.isReady()).toBe(true);
    });

    it('should handle double initialization gracefully', async () => {
      await bridge.init();
      await bridge.init(); // Should not throw
      expect(bridge.isReady()).toBe(true);
    });
  });

  describe('Lifecycle', () => {
    it('should destroy successfully', async () => {
      await bridge.init();
      await bridge.destroy();
      expect(bridge.isReady()).toBe(false);
    });

    it('should handle destroy when not initialized', async () => {
      await expect(bridge.destroy()).resolves.not.toThrow();
    });

    it('should reinitialize after destroy', async () => {
      await bridge.init();
      await bridge.destroy();
      await bridge.init();
      expect(bridge.isReady()).toBe(true);
    });
  });

  describe('trainOnHistory', () => {
    beforeEach(async () => {
      await bridge.init();
    });

    it('should train on test history', async () => {
      const history = [
        {
          testId: 'test-1',
          testName: 'test_auth',
          file: 'auth.test.ts',
          failureRate: 0.1,
          avgDuration: 150,
          affectedFiles: ['src/auth.ts'],
          results: [
            { status: 'passed', duration: 150 },
            { status: 'failed', duration: 200 },
          ],
        },
        {
          testId: 'test-2',
          testName: 'test_login',
          file: 'login.test.ts',
          failureRate: 0.3,
          avgDuration: 200,
          affectedFiles: ['src/auth.ts', 'src/login.ts'],
          results: [
            { status: 'failed', duration: 200 },
            { status: 'passed', duration: 180 },
          ],
        },
      ];

      const result = await bridge.trainOnHistory(history);

      // Returns the average loss
      expect(typeof result).toBe('number');
    });

    it('should handle empty history', async () => {
      const result = await bridge.trainOnHistory([]);

      // Returns 0 for empty history
      expect(result).toBe(0);
    });

    it('should handle large history batches', async () => {
      const history = Array(100).fill(null).map((_, i) => ({
        testId: `test-${i}`,
        testName: `test_${i}`,
        file: `test${i}.test.ts`,
        failureRate: Math.random(),
        avgDuration: Math.floor(Math.random() * 500),
        affectedFiles: [`src/file${i % 10}.ts`],
        results: [
          { status: Math.random() > 0.2 ? 'passed' : 'failed', duration: 100 },
          { status: Math.random() > 0.2 ? 'passed' : 'failed', duration: 150 },
        ],
      }));

      const result = await bridge.trainOnHistory(history);

      expect(typeof result).toBe('number');
    });

    it('should throw when not initialized', async () => {
      await bridge.destroy();
      const history = [{
        testId: 'test-1',
        testName: 'test',
        file: 'test.ts',
        failureRate: 0,
        avgDuration: 100,
        affectedFiles: [],
        results: [],
      }];

      await expect(bridge.trainOnHistory(history)).rejects.toThrow('Learning bridge not initialized');
    });
  });

  describe('predictFailingTests', () => {
    beforeEach(async () => {
      await bridge.init();
      // Train with some history first
      await bridge.trainOnHistory([
        {
          testId: 'test-auth',
          testName: 'test_auth',
          file: 'auth.test.ts',
          failureRate: 0.5,
          avgDuration: 150,
          affectedFiles: ['src/auth.ts'],
          results: [
            { status: 'failed', duration: 150 },
            { status: 'passed', duration: 140 },
          ],
        },
        {
          testId: 'test-user',
          testName: 'test_user',
          file: 'user.test.ts',
          failureRate: 0.1,
          avgDuration: 100,
          affectedFiles: ['src/user.ts'],
          results: [
            { status: 'passed', duration: 100 },
            { status: 'passed', duration: 95 },
          ],
        },
      ]);
    });

    it('should predict failing tests for changed files', async () => {
      const changes = [
        { file: 'src/auth.ts', type: 'modified' as const, linesAdded: 10, linesRemoved: 2 },
      ];

      const predictions = await bridge.predictFailingTests(changes, 10);

      expect(Array.isArray(predictions)).toBe(true);
      // May or may not have predictions depending on training
    });

    it('should return empty array for unknown files', async () => {
      const changes = [
        { file: 'src/completely-new-file.ts', type: 'added' as const, linesAdded: 50, linesRemoved: 0 },
      ];

      const predictions = await bridge.predictFailingTests(changes, 10);

      expect(Array.isArray(predictions)).toBe(true);
      // Should return empty for files with no test mapping
      expect(predictions.length).toBe(0);
    });

    it('should sort predictions by failure probability', async () => {
      const changes = [
        { file: 'src/auth.ts', type: 'modified' as const, linesAdded: 5, linesRemoved: 2 },
        { file: 'src/user.ts', type: 'modified' as const, linesAdded: 3, linesRemoved: 1 },
      ];

      const predictions = await bridge.predictFailingTests(changes, 10);

      if (predictions.length >= 2) {
        for (let i = 1; i < predictions.length; i++) {
          expect(predictions[i - 1].failureProbability).toBeGreaterThanOrEqual(
            predictions[i].failureProbability
          );
        }
      }
    });

    it('should throw when not initialized', async () => {
      await bridge.destroy();
      const changes = [{ file: 'test.ts', type: 'modified' as const, linesAdded: 1, linesRemoved: 0 }];

      await expect(bridge.predictFailingTests(changes, 10)).rejects.toThrow('Learning bridge not initialized');
    });
  });

  describe('updatePolicyWithFeedback', () => {
    beforeEach(async () => {
      await bridge.init();
      // First train with some history to have embeddings
      await bridge.trainOnHistory([
        {
          testId: 'test-auth',
          testName: 'test_auth',
          file: 'auth.test.ts',
          failureRate: 0.5,
          avgDuration: 150,
          affectedFiles: ['src/auth.ts'],
          results: [
            { status: 'passed', duration: 150 },
            { status: 'failed', duration: 200 },
          ],
        },
      ]);
    });

    it('should update policy with feedback', async () => {
      const feedback = {
        predictions: [
          { testId: 'test-auth', failureProbability: 0.8, confidence: 0.7, reason: 'test' },
        ],
        actualResults: [
          { testId: 'test-auth', status: 'failed' },
        ],
      };

      // Should not throw
      await expect(bridge.updatePolicyWithFeedback(feedback)).resolves.not.toThrow();
    });

    it('should throw when not initialized', async () => {
      await bridge.destroy();

      await expect(
        bridge.updatePolicyWithFeedback({
          predictions: [],
          actualResults: [],
        })
      ).rejects.toThrow('Learning bridge not initialized');
    });
  });

  describe('isReady', () => {
    it('should return false before init', () => {
      expect(bridge.isReady()).toBe(false);
    });

    it('should return true after init', async () => {
      await bridge.init();
      expect(bridge.isReady()).toBe(true);
    });

    it('should return false after destroy', async () => {
      await bridge.init();
      await bridge.destroy();
      expect(bridge.isReady()).toBe(false);
    });
  });

  describe('JavaScript Fallback', () => {
    it('should work without WASM', async () => {
      // WASM is mocked to be unavailable
      const fallbackBridge = new TestLearningBridge();
      await fallbackBridge.init();

      expect(fallbackBridge.isReady()).toBe(true);

      const changes = [{ file: 'test.ts', type: 'modified' as const, linesAdded: 1, linesRemoved: 0 }];
      const predictions = await fallbackBridge.predictFailingTests(changes, 10);
      expect(Array.isArray(predictions)).toBe(true);

      await fallbackBridge.destroy();
    });

    it('should provide consistent results in fallback mode', async () => {
      const bridge1 = new TestLearningBridge();
      const bridge2 = new TestLearningBridge();

      await bridge1.init();
      await bridge2.init();

      const history = [{
        testId: 'test-1',
        testName: 'test',
        file: 'test.ts',
        failureRate: 0.1,
        avgDuration: 100,
        affectedFiles: ['src/a.ts'],
        results: [
          { status: 'passed', duration: 100 },
          { status: 'passed', duration: 95 },
        ],
      }];

      await bridge1.trainOnHistory(history);
      await bridge2.trainOnHistory(history);

      const changes = [{ file: 'src/a.ts', type: 'modified' as const, linesAdded: 5, linesRemoved: 2 }];
      const pred1 = await bridge1.predictFailingTests(changes, 10);
      const pred2 = await bridge2.predictFailingTests(changes, 10);

      // Both should produce same number of predictions
      expect(pred1.length).toBe(pred2.length);

      await bridge1.destroy();
      await bridge2.destroy();
    });
  });

  describe('Error Handling', () => {
    it('should handle concurrent operations', async () => {
      await bridge.init();

      const history = [{
        testId: 't1',
        testName: 't',
        file: 't.ts',
        failureRate: 0.1,
        avgDuration: 1,
        affectedFiles: ['a.ts'],
        results: [{ status: 'passed', duration: 1 }],
      }];

      const changes = [{ file: 'a.ts', type: 'modified' as const, linesAdded: 1, linesRemoved: 0 }];

      const operations = [
        bridge.trainOnHistory(history),
        bridge.predictFailingTests(changes, 10),
      ];

      await expect(Promise.all(operations)).resolves.toBeDefined();
    });
  });

  describe('Memory Management', () => {
    it('should release resources on destroy', async () => {
      await bridge.init();

      // Train with substantial data
      const largeHistory = Array(50).fill(null).map((_, i) => ({
        testId: `test-${i}`,
        testName: `test_${i}`,
        file: `test${i}.test.ts`,
        failureRate: Math.random(),
        avgDuration: Math.floor(Math.random() * 1000),
        affectedFiles: Array(5).fill(null).map((_, j) => `src/file${j}.ts`),
        results: [
          { status: Math.random() > 0.3 ? 'passed' : 'failed', duration: 100 },
          { status: Math.random() > 0.3 ? 'passed' : 'failed', duration: 110 },
        ],
      }));

      await bridge.trainOnHistory(largeHistory);
      await bridge.destroy();

      // Bridge should be reset
      expect(bridge.isReady()).toBe(false);
    });

    it('should handle multiple init/destroy cycles', async () => {
      for (let i = 0; i < 5; i++) {
        await bridge.init();
        expect(bridge.isReady()).toBe(true);
        await bridge.destroy();
        expect(bridge.isReady()).toBe(false);
      }
    });
  });
});
