/**
 * Coverage Router Tests
 *
 * Tests for the coverage-based test routing functionality.
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { CoverageRouter, createCoverageRouter, type CoverageReport, type FileCoverage } from '../../src/ruvector/coverage-router';

// Mock the @ruvector/coverage module
vi.mock('@ruvector/coverage', () => ({
  createCoverageRouter: vi.fn(() => null),
}));

describe('CoverageRouter', () => {
  let router: CoverageRouter;

  beforeEach(() => {
    router = new CoverageRouter();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe('constructor', () => {
    it('should create router with default config', () => {
      const stats = router.getStats();
      expect(stats.minCoverage).toBe(70);
      expect(stats.targetCoverage).toBe(85);
    });

    it('should create router with custom config', () => {
      const customRouter = new CoverageRouter({
        minCoverage: 80,
        targetCoverage: 95,
        incremental: false,
      });
      const stats = customRouter.getStats();
      expect(stats.minCoverage).toBe(80);
      expect(stats.targetCoverage).toBe(95);
    });
  });

  describe('initialize', () => {
    it('should initialize without ruvector (fallback mode)', async () => {
      await router.initialize();
      const stats = router.getStats();
      expect(stats.useNative).toBe(false);
    });
  });

  describe('parseCoverage', () => {
    describe('JSON format', () => {
      it('should parse JSON coverage data', () => {
        const data = {
          'src/utils.ts': {
            lineCoverage: 80,
            branchCoverage: 70,
            functionCoverage: 90,
            statementCoverage: 85,
            uncoveredLines: [10, 20, 30],
            totalLines: 100,
            coveredLines: 80,
          },
        };
        
        const report = router.parseCoverage(data, 'json');
        expect(report).toHaveProperty('overall');
        expect(report).toHaveProperty('byType');
        expect(report).toHaveProperty('byFile');
        expect(report.byFile.length).toBe(1);
      });

      it('should parse array of FileCoverage', () => {
        const data: FileCoverage[] = [
          { path: 'src/a.ts', lineCoverage: 80, branchCoverage: 70, functionCoverage: 90, statementCoverage: 85, uncoveredLines: [], totalLines: 100, coveredLines: 80 },
          { path: 'src/b.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 65, uncoveredLines: [], totalLines: 50, coveredLines: 30 },
        ];
        
        const report = router.parseCoverage(data, 'json');
        expect(report.byFile.length).toBe(2);
      });
    });

    describe('LCOV format', () => {
      it('should parse LCOV data', () => {
        const lcov = `SF:src/utils.ts
LF:100
LH:80
DA:1,1
DA:2,1
DA:3,0
end_of_record
SF:src/helper.ts
LF:50
LH:40
end_of_record
`;
        
        const report = router.parseCoverage(lcov, 'lcov');
        expect(report.byFile.length).toBe(2);
        expect(report.byFile[0].path).toBe('src/utils.ts');
      });

      it('should extract uncovered lines from LCOV', () => {
        const lcov = `SF:src/utils.ts
LF:10
LH:8
DA:1,1
DA:2,0
DA:3,1
DA:4,0
end_of_record
`;
        
        const report = router.parseCoverage(lcov, 'lcov');
        expect(report.byFile[0].uncoveredLines).toContain(2);
        expect(report.byFile[0].uncoveredLines).toContain(4);
      });
    });

    describe('Istanbul format', () => {
      it('should parse Istanbul coverage data', () => {
        const istanbul = {
          'src/utils.ts': {
            s: { '0': 1, '1': 0, '2': 1 },
            f: { '0': 1, '1': 0 },
            b: { '0': [1, 0] },
          },
        };
        
        const report = router.parseCoverage(istanbul, 'istanbul');
        expect(report.byFile.length).toBe(1);
        expect(report.byFile[0].statementCoverage).toBeCloseTo(66.67, 1);
      });
    });

    describe('Cobertura format', () => {
      it('should parse Cobertura XML data', () => {
        const cobertura = `
<coverage>
  <packages>
    <package>
      <classes>
        <class filename="src/utils.ts" line-rate="0.8" branch-rate="0.7">
        </class>
      </classes>
    </package>
  </packages>
</coverage>
`;
        
        const report = router.parseCoverage(cobertura, 'cobertura');
        expect(report.byFile.length).toBe(1);
        expect(report.byFile[0].lineCoverage).toBe(80);
        expect(report.byFile[0].branchCoverage).toBe(70);
      });
    });
  });

  describe('route', () => {
    it('should return CoverageRouteResult', () => {
      const report: CoverageReport = {
        overall: 75,
        byType: { line: 75, branch: 70, function: 80, statement: 75 },
        byFile: [
          { path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 50 },
          { path: 'src/high.ts', lineCoverage: 90, branchCoverage: 85, functionCoverage: 95, statementCoverage: 90, uncoveredLines: [], totalLines: 100, coveredLines: 90 },
        ],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report);
      expect(result).toHaveProperty('action');
      expect(result).toHaveProperty('priority');
      expect(result).toHaveProperty('targetFiles');
      expect(result).toHaveProperty('testTypes');
      expect(result).toHaveProperty('gaps');
      expect(result).toHaveProperty('estimatedEffort');
      expect(result).toHaveProperty('impactScore');
    });

    it('should return prioritize action for low coverage', () => {
      const report: CoverageReport = {
        overall: 50,
        byType: { line: 50, branch: 40, function: 60, statement: 50 },
        byFile: [{ path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 }],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report);
      expect(result.action).toBe('prioritize');
    });

    it('should return skip action for high coverage', () => {
      const report: CoverageReport = {
        overall: 95,
        byType: { line: 95, branch: 90, function: 98, statement: 95 },
        byFile: [{ path: 'src/high.ts', lineCoverage: 95, branchCoverage: 90, functionCoverage: 98, statementCoverage: 95, uncoveredLines: [], totalLines: 100, coveredLines: 95 }],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report);
      expect(result.action).toBe('skip');
    });

    it('should identify coverage gaps', () => {
      const report: CoverageReport = {
        overall: 75,
        byType: { line: 75, branch: 70, function: 80, statement: 75 },
        byFile: [
          { path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 50 },
        ],
        lowestCoverage: [{ path: 'src/low.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 50 }],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report);
      expect(result.gaps.length).toBeGreaterThan(0);
      expect(result.gaps[0].file).toBe('src/low.ts');
      expect(result.gaps[0].gap).toBeGreaterThan(0);
    });

    it('should prioritize changed files', () => {
      const report: CoverageReport = {
        overall: 80,
        byType: { line: 80, branch: 75, function: 85, statement: 80 },
        byFile: [
          { path: 'src/changed.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 60, uncoveredLines: [], totalLines: 100, coveredLines: 60 },
          { path: 'src/unchanged.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 },
        ],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report, ['changed.ts']);
      expect(result.targetFiles[0]).toContain('changed');
    });

    it('should recommend test types based on files', () => {
      const report: CoverageReport = {
        overall: 75,
        byType: { line: 75, branch: 70, function: 80, statement: 75 },
        byFile: [
          { path: 'src/api/handler.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 },
        ],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report);
      expect(result.testTypes).toContain('unit');
      expect(result.testTypes).toContain('integration');
    });

    it('should calculate estimated effort', () => {
      const report: CoverageReport = {
        overall: 75,
        byType: { line: 75, branch: 70, function: 80, statement: 75 },
        byFile: [
          { path: 'src/a.ts', lineCoverage: 50, branchCoverage: 40, functionCoverage: 60, statementCoverage: 50, uncoveredLines: [], totalLines: 100, coveredLines: 50 },
          { path: 'src/b.ts', lineCoverage: 60, branchCoverage: 50, functionCoverage: 70, statementCoverage: 60, uncoveredLines: [], totalLines: 100, coveredLines: 60 },
        ],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };
      
      const result = router.route(report);
      expect(result.estimatedEffort).toBeGreaterThan(0);
    });
  });

  describe('getTrend', () => {
    it('should return stable for insufficient history', () => {
      const trend = router.getTrend();
      expect(trend.direction).toBe('stable');
      expect(trend.change).toBe(0);
    });

    it('should detect upward trend', () => {
      const report1: CoverageReport = { overall: 70, byType: { line: 70, branch: 65, function: 75, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      const report2: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      
      router.addToHistory(report1);
      router.addToHistory(report2);
      
      const trend = router.getTrend();
      expect(trend.direction).toBe('up');
      expect(trend.change).toBe(10);
    });

    it('should detect downward trend', () => {
      const report1: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      const report2: CoverageReport = { overall: 70, byType: { line: 70, branch: 65, function: 75, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      
      router.addToHistory(report1);
      router.addToHistory(report2);
      
      const trend = router.getTrend();
      expect(trend.direction).toBe('down');
      expect(trend.change).toBe(-10);
    });

    it('should detect stable trend for small changes', () => {
      const report1: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      const report2: CoverageReport = { overall: 80.3, byType: { line: 80.3, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      
      router.addToHistory(report1);
      router.addToHistory(report2);
      
      const trend = router.getTrend();
      expect(trend.direction).toBe('stable');
    });
  });

  describe('addToHistory', () => {
    it('should add report to history', () => {
      const report: CoverageReport = { overall: 80, byType: { line: 80, branch: 75, function: 85, statement: 80 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
      
      router.addToHistory(report);
      expect(router.getStats().historySize).toBe(1);
    });

    it('should limit history to 10 entries', () => {
      for (let i = 0; i < 15; i++) {
        const report: CoverageReport = { overall: 70 + i, byType: { line: 70, branch: 65, function: 75, statement: 70 }, byFile: [], lowestCoverage: [], highestCoverage: [], uncoveredCritical: [], timestamp: Date.now() };
        router.addToHistory(report);
      }
      
      expect(router.getStats().historySize).toBe(10);
    });
  });

  describe('critical file detection', () => {
    it('should identify uncovered critical files', () => {
      const data = {
        'src/auth/login.ts': {
          lineCoverage: 50,
          branchCoverage: 40,
          functionCoverage: 60,
          statementCoverage: 50,
          uncoveredLines: [1, 2, 3],
          totalLines: 100,
          coveredLines: 50,
        },
        'src/payment/checkout.ts': {
          lineCoverage: 60,
          branchCoverage: 50,
          functionCoverage: 70,
          statementCoverage: 60,
          uncoveredLines: [1, 2],
          totalLines: 100,
          coveredLines: 60,
        },
      };
      
      const report = router.parseCoverage(data, 'json');
      expect(report.uncoveredCritical.length).toBeGreaterThan(0);
    });
  });

  describe('edge cases', () => {
    it('should handle empty coverage data', () => {
      const report = router.parseCoverage({}, 'json');
      expect(report.byFile).toHaveLength(0);
      expect(report.overall).toBe(100);
    });

    it('should handle 100% coverage', () => {
      const data = {
        'src/perfect.ts': {
          lineCoverage: 100,
          branchCoverage: 100,
          functionCoverage: 100,
          statementCoverage: 100,
          uncoveredLines: [],
          totalLines: 100,
          coveredLines: 100,
        },
      };
      
      const report = router.parseCoverage(data, 'json');
      const result = router.route(report);
      expect(result.action).toBe('skip');
      expect(result.gaps).toHaveLength(0);
    });

    it('should handle 0% coverage', () => {
      const data = {
        'src/empty.ts': {
          lineCoverage: 0,
          branchCoverage: 0,
          functionCoverage: 0,
          statementCoverage: 0,
          uncoveredLines: Array.from({ length: 100 }, (_, i) => i + 1),
          totalLines: 100,
          coveredLines: 0,
        },
      };
      
      const report = router.parseCoverage(data, 'json');
      const result = router.route(report);
      expect(result.action).toBe('prioritize');
      expect(result.priority).toBeGreaterThanOrEqual(8);
    });
  });
});

describe('createCoverageRouter', () => {
  it('should create router instance', () => {
    const router = createCoverageRouter();
    expect(router).toBeInstanceOf(CoverageRouter);
  });

  it('should accept config', () => {
    const router = createCoverageRouter({ minCoverage: 80 });
    expect(router).toBeInstanceOf(CoverageRouter);
  });
});

describe('CoverageRouter Advanced Scenarios', () => {
  let router: CoverageRouter;

  beforeEach(() => {
    router = new CoverageRouter();
  });

  describe('incremental coverage', () => {
    it('should track coverage changes over time', () => {
      const report1: CoverageReport = {
        overall: 70,
        byType: { line: 70, branch: 65, function: 75, statement: 70 },
        byFile: [
          { path: 'src/a.ts', lineCoverage: 70, branchCoverage: 65, functionCoverage: 75, statementCoverage: 70, uncoveredLines: [1,2,3], totalLines: 100, coveredLines: 70 },
        ],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now() - 1000,
      };

      const report2: CoverageReport = {
        overall: 80,
        byType: { line: 80, branch: 75, function: 85, statement: 80 },
        byFile: [
          { path: 'src/a.ts', lineCoverage: 80, branchCoverage: 75, functionCoverage: 85, statementCoverage: 80, uncoveredLines: [1,2], totalLines: 100, coveredLines: 80 },
        ],
        lowestCoverage: [],
        highestCoverage: [],
        uncoveredCritical: [],
        timestamp: Date.now(),
      };

      router.addToHistory(report1);
      router.addToHistory(report2);

      const trend = router.getTrend();
      expect(trend.direction).toBe('up');
      expect(trend.change).toBe(10);
    });
  });

  describe('priority scoring', () => {
    it('should prioritize critical files with low coverage', () => {
      const report = router.parseCoverage({
        'src/auth/login.ts': {
          lineCoverage: 30,
          branchCoverage: 20,
          functionCoverage: 40,
          statementCoverage: 30,
          uncoveredLines: Array.from({ length: 70 }, (_, i) => i + 1),
          totalLines: 100,
          coveredLines: 30,
        },
        'src/utils/helper.ts': {
          lineCoverage: 30,
          branchCoverage: 20,
          functionCoverage: 40,
          statementCoverage: 30,
          uncoveredLines: Array.from({ length: 70 }, (_, i) => i + 1),
          totalLines: 100,
          coveredLines: 30,
        },
      }, 'json');

      const result = router.route(report);

      // Auth file should be prioritized over utils
      const authIndex = result.targetFiles.findIndex(f => f.includes('auth'));
      const utilsIndex = result.targetFiles.findIndex(f => f.includes('utils'));

      if (authIndex !== -1 && utilsIndex !== -1) {
        expect(authIndex).toBeLessThan(utilsIndex);
      }
    });

    it('should consider file complexity in priority', () => {
      const report = router.parseCoverage({
        'src/simple.ts': {
          lineCoverage: 60,
          branchCoverage: 50,
          functionCoverage: 70,
          statementCoverage: 60,
          uncoveredLines: [1, 2, 3, 4],
          totalLines: 10,
          coveredLines: 6,
        },
        'src/complex.ts': {
          lineCoverage: 60,
          branchCoverage: 50,
          functionCoverage: 70,
          statementCoverage: 60,
          uncoveredLines: Array.from({ length: 40 }, (_, i) => i + 1),
          totalLines: 100,
          coveredLines: 60,
        },
      }, 'json');

      const result = router.route(report);

      // Both should be in target files since both are below threshold
      expect(result.targetFiles.length).toBeGreaterThan(0);
    });
  });

  describe('test type recommendations', () => {
    it('should recommend e2e tests for UI files', () => {
      const report = router.parseCoverage({
        'src/components/Button.tsx': {
          lineCoverage: 50,
          branchCoverage: 40,
          functionCoverage: 60,
          statementCoverage: 50,
          uncoveredLines: [1, 2, 3],
          totalLines: 100,
          coveredLines: 50,
        },
      }, 'json');

      const result = router.route(report);
      expect(result.testTypes).toContain('unit');
    });

    it('should recommend integration tests for API handlers', () => {
      const report = router.parseCoverage({
        'src/api/users/handler.ts': {
          lineCoverage: 50,
          branchCoverage: 40,
          functionCoverage: 60,
          statementCoverage: 50,
          uncoveredLines: [1, 2, 3],
          totalLines: 100,
          coveredLines: 50,
        },
      }, 'json');

      const result = router.route(report);
      expect(result.testTypes).toContain('integration');
    });
  });

  describe('gap analysis', () => {
    it('should identify branch coverage gaps', () => {
      const report = router.parseCoverage({
        'src/logic.ts': {
          lineCoverage: 50,  // Below threshold
          branchCoverage: 30,  // Significant branch coverage gap
          functionCoverage: 50,
          statementCoverage: 50,
          uncoveredLines: [1, 2, 3, 4, 5],
          totalLines: 100,
          coveredLines: 50,
        },
      }, 'json');

      const result = router.route(report);

      // Should have some gaps since coverage is below threshold
      expect(result.gaps.length >= 0).toBe(true);
    });

    it('should calculate gap delta from target', () => {
      const customRouter = new CoverageRouter({ targetCoverage: 90 });

      const report = customRouter.parseCoverage({
        'src/file.ts': {
          lineCoverage: 70,
          branchCoverage: 60,
          functionCoverage: 80,
          statementCoverage: 70,
          uncoveredLines: [1, 2, 3],
          totalLines: 100,
          coveredLines: 70,
        },
      }, 'json');

      const result = customRouter.route(report);

      // Gap should be calculated from target (90 - 70 = 20)
      expect(result.gaps.some(g => g.gap >= 15)).toBe(true);
    });
  });

  describe('effort estimation', () => {
    it('should estimate higher effort for more uncovered lines', () => {
      const smallGapReport = router.parseCoverage({
        'src/small.ts': {
          lineCoverage: 95,
          branchCoverage: 90,
          functionCoverage: 100,
          statementCoverage: 95,
          uncoveredLines: [1],
          totalLines: 20,
          coveredLines: 19,
        },
      }, 'json');

      const largeGapReport = router.parseCoverage({
        'src/large.ts': {
          lineCoverage: 50,
          branchCoverage: 40,
          functionCoverage: 60,
          statementCoverage: 50,
          uncoveredLines: Array.from({ length: 50 }, (_, i) => i + 1),
          totalLines: 100,
          coveredLines: 50,
        },
      }, 'json');

      const smallResult = router.route(smallGapReport);
      const largeResult = router.route(largeGapReport);

      expect(largeResult.estimatedEffort).toBeGreaterThan(smallResult.estimatedEffort);
    });
  });

  describe('format handling', () => {
    it('should handle clover format', () => {
      const clover = `<?xml version="1.0" encoding="UTF-8"?>
<coverage>
  <project>
    <file path="src/utils.ts">
      <line num="1" type="stmt" count="1"/>
      <line num="2" type="stmt" count="0"/>
      <metrics loc="10" ncloc="8" statements="8" coveredstatements="6"/>
    </file>
  </project>
</coverage>`;

      // This may or may not be supported depending on implementation
      try {
        const report = router.parseCoverage(clover, 'cobertura');
        expect(report).toBeDefined();
      } catch {
        // Clover format not supported is acceptable — no assertion needed
      }
    });

    it('should handle malformed LCOV gracefully', () => {
      const malformedLcov = `
not valid lcov format
SF:file.ts
missing end_of_record
random garbage
`;
      const report = router.parseCoverage(malformedLcov, 'lcov');
      // Should not throw and return some result
      expect(report).toBeDefined();
    });
  });

  describe('impact scoring', () => {
    it('should calculate impact score based on file criticality', () => {
      const report1 = router.parseCoverage({
        'src/auth/jwt.ts': {
          lineCoverage: 50,
          branchCoverage: 40,
          functionCoverage: 60,
          statementCoverage: 50,
          uncoveredLines: [1, 2, 3],
          totalLines: 100,
          coveredLines: 50,
        },
      }, 'json');

      const report2 = router.parseCoverage({
        'src/utils/format.ts': {
          lineCoverage: 50,
          branchCoverage: 40,
          functionCoverage: 60,
          statementCoverage: 50,
          uncoveredLines: [1, 2, 3],
          totalLines: 100,
          coveredLines: 50,
        },
      }, 'json');

      const result1 = router.route(report1);
      const result2 = router.route(report2);

      // Auth file should have higher impact score
      expect(result1.impactScore).toBeGreaterThanOrEqual(result2.impactScore);
    });
  });
});
