/**
 * Tests for DependencyAnalyzer
 */

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { DependencyAnalyzer } from './dependency-analyzer';

describe('DependencyAnalyzer', () => {
  let analyzer: DependencyAnalyzer;
  let testDir: string;

  beforeEach(() => {
    analyzer = new DependencyAnalyzer();
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dep-analyzer-test-'));
  });

  afterEach(() => {
    // Clean up test directory
    if (fs.existsSync(testDir)) {
      fs.rmSync(testDir, { recursive: true, force: true });
    }
  });

  describe('supportsLanguage', () => {
    it('should support TypeScript/JavaScript technologies', () => {
      const supported = [
        'typescript',
        'javascript',
        'typescript/express',
        'typescript/node',
        'react',
        'next',
        'vue',
        'angular',
        'nest',
        'fastify',
      ];

      for (const tech of supported) {
        const result = analyzer.analyzeComponent(testDir, 'test-comp', 'backend', tech);
        expect(result.errors).not.toContain(
          expect.stringContaining('not supported for')
        );
      }
    });

    it('should not support non-TypeScript languages', () => {
      const unsupported = ['rust', 'go', 'python', 'java', 'ruby'];

      for (const tech of unsupported) {
        const result = analyzer.analyzeComponent(testDir, 'test-comp', 'backend', tech);
        expect(result.errors?.length).toBeGreaterThan(0);
        expect(result.errors?.[0]).toContain('not supported for');
        expect(result.detectedDependencies).toEqual([]);
      }
    });
  });

  describe('analyzeComponent', () => {
    it('should detect service client imports with default pattern', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      // Create a file with redis-client import
      const filePath = path.join(componentDir, 'index.ts');
      fs.writeFileSync(
        filePath,
        `
import { redisClient } from './clients/redis-client';

export async function getUser(id: string) {
  const cached = await redisClient.get(\`user:\${id}\`);
  if (cached) return JSON.parse(cached);
  // ...
}
`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toHaveLength(1);
      expect(result.detectedDependencies[0].service).toBe('redis');
      expect(result.detectedDependencies[0].files.some(f => f.includes('index.ts'))).toBe(true);
      expect(result.detectedDependencies[0].imports).toContain('./clients/redis-client');
    });

    it('should detect multiple service imports in same file', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      const filePath = path.join(componentDir, 'service.ts');
      fs.writeFileSync(
        filePath,
        `
import { redisClient } from './clients/redis-client';
import { queueClient } from './clients/queue-client';
import { emailClient } from './clients/email-client';

export class BackgroundService {
  async process(job: any) {
    await redisClient.set('key', 'value');
    await queueClient.enqueue('task', job);
    await emailClient.send(job.email, 'Hello');
  }
}
`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toHaveLength(3);

      const services = result.detectedDependencies.map(d => d.service).sort();
      expect(services).toEqual(['email', 'queue', 'redis']);
    });

    it('should detect services across multiple files', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      // File 1: redis
      fs.writeFileSync(
        path.join(componentDir, 'cache.ts'),
        `import { redisClient } from './clients/redis-client';`
      );

      // File 2: queue
      fs.writeFileSync(
        path.join(componentDir, 'worker.ts'),
        `import { queueClient } from './clients/queue-client';`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toHaveLength(2);

      const redisDep = result.detectedDependencies.find(d => d.service === 'redis');
      expect(redisDep?.files.some(f => f.includes('cache.ts'))).toBe(true);

      const queueDep = result.detectedDependencies.find(d => d.service === 'queue');
      expect(queueDep?.files.some(f => f.includes('worker.ts'))).toBe(true);
    });

    it('should handle alternative import patterns', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      const filePath = path.join(componentDir, 'service.ts');
      fs.writeFileSync(
        filePath,
        `
// Pattern 1: Named import
import { redisClient } from './clients/redis-client';

// Pattern 2: Import * as
import * as redis from './clients/redis-client';

// Pattern 3: Default import
import queueClient from './clients/queue-client';

// Pattern 4: Relative path without ./
import { emailClient } from 'clients/email-client';
`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      const services = result.detectedDependencies.map(d => d.service).sort();
      expect(services).toEqual(['email', 'queue', 'redis']);
    });

    it('should not detect external package imports as services', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      const filePath = path.join(componentDir, 'index.ts');
      fs.writeFileSync(
        filePath,
        `
import express from 'express';
import { redisClient } from './clients/redis-client';
import axios from 'axios';
`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toHaveLength(1);
      expect(result.detectedDependencies[0].service).toBe('redis');
    });

    it('should return empty array when no services detected', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      const filePath = path.join(componentDir, 'index.ts');
      fs.writeFileSync(
        filePath,
        `
import express from 'express';

export const app = express();
`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toEqual([]);
    });

    it('should handle component directory that does not exist', () => {
      const result = analyzer.analyzeComponent(
        testDir,
        'nonexistent-component',
        'backend',
        'typescript/express'
      );

      // Should not throw and return empty results
      expect(result).toBeDefined();
      expect(result.detectedDependencies).toEqual([]);
    });

    it('should handle component with no src directory', () => {
      // Create component dir but no src
      fs.mkdirSync(path.join(testDir, 'api'));

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      // Should not throw and return empty results
      expect(result).toBeDefined();
      expect(result.detectedDependencies).toEqual([]);
    });

    it('should skip non-TypeScript files', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      // Create a JSON file (should be skipped)
      fs.writeFileSync(
        path.join(componentDir, 'config.json'),
        JSON.stringify({ redis: true })
      );

      // Create a TS file (should be analyzed)
      fs.writeFileSync(
        path.join(componentDir, 'index.ts'),
        `import { redisClient } from './clients/redis-client';`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toHaveLength(1);
      expect(result.detectedDependencies[0].service).toBe('redis');
    });

    it('should handle files with syntax errors gracefully', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      fs.mkdirSync(componentDir, { recursive: true });

      // Create a file with invalid syntax
      fs.writeFileSync(
        path.join(componentDir, 'broken.ts'),
        `
import { redisClient from './clients/redis-client'; // Missing closing brace
export const x = {
`
      );

      // Should not throw, should return empty or partial results
      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result).toBeDefined();
      // The analyzer should still try to parse what it can
    });
  });

  describe('compareDependencies', () => {
    it('should return no warnings when expected and detected match', () => {
      const expected = [
        { service: 'redis', usage: 'Caching' },
        { service: 'queue', usage: 'Background jobs' },
      ];

      const detected = [
        { service: 'redis', files: ['cache.ts'], imports: ['./redis-client'] },
        { service: 'queue', files: ['worker.ts'], imports: ['./queue-client'] },
      ];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toEqual([]);
    });

    it('should warn about missing dependencies', () => {
      const expected = [
        { service: 'redis', usage: 'Caching' },
        { service: 'queue', usage: 'Background jobs' },
      ];

      const detected = [
        { service: 'redis', files: ['cache.ts'], imports: ['./redis-client'] },
      ];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toHaveLength(1);
      expect(warnings[0]).toContain('dependency');
      expect(warnings[0]).toContain('queue');
    });

    it('should warn about unexpected dependencies', () => {
      const expected = [
        { service: 'redis', usage: 'Caching' },
      ];

      const detected = [
        { service: 'redis', files: ['cache.ts'], imports: ['./redis-client'] },
        { service: 'queue', files: ['worker.ts'], imports: ['./queue-client'] },
      ];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toHaveLength(1);
      expect(warnings[0]).toContain('Unexpected dependency');
      expect(warnings[0]).toContain('queue');
      expect(warnings[0]).toContain('worker.ts');
    });

    it('should warn about both missing and unexpected dependencies', () => {
      const expected = [
        { service: 'redis', usage: 'Caching' },
        { service: 'postgres', usage: 'Database' },
      ];

      const detected = [
        { service: 'redis', files: ['cache.ts'], imports: ['./redis-client'] },
        { service: 'queue', files: ['worker.ts'], imports: ['./queue-client'] },
      ];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toHaveLength(2);

      const missingWarning = warnings.find(w => w.includes('dependency') && w.includes('postgres'));
      expect(missingWarning).toBeDefined();

      const unexpectedWarning = warnings.find(w => w.includes('Unexpected'));
      expect(unexpectedWarning).toBeDefined();
      expect(unexpectedWarning).toContain('queue');
    });

    it('should handle empty expected dependencies', () => {
      const expected: Array<{ service: string; usage: string }> = [];

      const detected = [
        { service: 'redis', files: ['cache.ts'], imports: ['./redis-client'] },
      ];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toHaveLength(1);
      expect(warnings[0]).toContain('Unexpected dependency');
      expect(warnings[0]).toContain('redis');
    });

    it('should handle empty detected dependencies', () => {
      const expected = [
        { service: 'redis', usage: 'Caching' },
      ];

      const detected: Array<{ service: string; files: string[]; imports: string[] }> = [];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toHaveLength(1);
      expect(warnings[0]).toContain('dependency');
      expect(warnings[0]).toContain('redis');
    });

    it('should handle both empty arrays', () => {
      const expected: Array<{ service: string; usage: string }> = [];
      const detected: Array<{ service: string; files: string[]; imports: string[] }> = [];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toEqual([]);
    });

    it('should list multiple files where unexpected dependency was detected', () => {
      const expected: Array<{ service: string; usage: string }> = [];

      const detected = [
        { service: 'redis', files: ['cache.ts', 'session.ts', 'worker.ts'], imports: ['./redis-client'] },
      ];

      const warnings = analyzer.compareDependencies(expected, detected);

      expect(warnings).toHaveLength(1);
      expect(warnings[0]).toContain('cache.ts');
      expect(warnings[0]).toContain('session.ts');
      expect(warnings[0]).toContain('worker.ts');
    });
  });

  describe('integration tests', () => {
    it('should analyze a realistic backend component', () => {
      const componentDir = path.join(testDir, 'api', 'src');
      const clientsDir = path.join(componentDir, 'clients');
      const routesDir = path.join(componentDir, 'routes');

      fs.mkdirSync(clientsDir, { recursive: true });
      fs.mkdirSync(routesDir, { recursive: true });

      // Create redis client (orchestrator file)
      fs.writeFileSync(
        path.join(clientsDir, 'redis-client.ts'),
        `
export const redisClient = {
  get: async (key: string) => null,
  set: async (key: string, value: string) => {},
};
`
      );

      // Create queue client (orchestrator file)
      fs.writeFileSync(
        path.join(clientsDir, 'queue-client.ts'),
        `
export const queueClient = {
  enqueue: async (job: string, data: any) => {},
};
`
      );

      // Create route that uses redis
      fs.writeFileSync(
        path.join(routesDir, 'users.ts'),
        `
import { redisClient } from '../clients/redis-client';

export async function getUser(id: string) {
  return await redisClient.get(\`user:\${id}\`);
}
`
      );

      // Create route that uses queue
      fs.writeFileSync(
        path.join(routesDir, 'jobs.ts'),
        `
import { queueClient } from '../clients/queue-client';

export async function createJob(data: any) {
  await queueClient.enqueue('process', data);
}
`
      );

      const result = analyzer.analyzeComponent(testDir, 'api', 'backend', 'typescript/express');

      expect(result.detectedDependencies).toHaveLength(2);

      const services = result.detectedDependencies.map(d => d.service).sort();
      expect(services).toEqual(['queue', 'redis']);

      // Check that files are correctly associated
      const redisDep = result.detectedDependencies.find(d => d.service === 'redis');
      expect(redisDep?.files.some(f => f.includes('users.ts'))).toBe(true);

      const queueDep = result.detectedDependencies.find(d => d.service === 'queue');
      expect(queueDep?.files.some(f => f.includes('jobs.ts'))).toBe(true);
    });
  });
});
