/**
 * Dependency Analyzer
 * Analyzes TypeScript code to detect actual infrastructure service dependencies
 * Uses TypeScript AST to find service client imports and usage
 */

import * as ts from 'typescript';
import * as fs from 'fs';
import * as path from 'path';

export interface DetectedDependency {
  service: string; // Service name (redis, queue, postgres, etc.)
  files: string[]; // Files where this service is used
  imports: string[]; // Import statements found
}

export interface DependencyAnalysisResult {
  componentName: string;
  detectedDependencies: DetectedDependency[];
  errors?: string[];
}

export class DependencyAnalyzer {
  /**
   * Check if dependency analysis is supported for this technology
   */
  private supportsLanguage(technology: string): boolean {
    const supportedLanguages = [
      'typescript',
      'javascript',
      'node',
      'express',
      'fastify',
      'nest',
      'react',
      'vue',
      'angular',
      'next',
    ];

    const techLower = technology.toLowerCase();
    return supportedLanguages.some(lang => techLower.includes(lang));
  }

  /**
   * Analyze a component's implementation to detect service dependencies
   */
  analyzeComponent(
    outputDir: string,
    componentName: string,
    componentType: string,
    technology?: string
  ): DependencyAnalysisResult {
    const result: DependencyAnalysisResult = {
      componentName,
      detectedDependencies: [],
      errors: [],
    };

    // Check if we support this technology
    if (technology && !this.supportsLanguage(technology)) {
      result.errors = [
        `Dependency analysis not supported for '${technology}' - only TypeScript/JavaScript analysis is available. ` +
        `Please manually verify dependencies match your YAML configuration.`
      ];
      return result;
    }

    // Determine where to look for implementation files based on component type
    const searchPaths = this.getSearchPaths(outputDir, componentName, componentType);

    // Track detected dependencies
    const dependencyMap = new Map<string, DetectedDependency>();

    for (const searchPath of searchPaths) {
      if (!fs.existsSync(searchPath)) {
        continue;
      }

      const files = this.findTypeScriptFiles(searchPath);

      for (const file of files) {
        try {
          const dependencies = this.analyzeFile(file);

          for (const dep of dependencies) {
            if (!dependencyMap.has(dep.service)) {
              dependencyMap.set(dep.service, {
                service: dep.service,
                files: [],
                imports: [],
              });
            }

            const existing = dependencyMap.get(dep.service)!;
            if (!existing.files.includes(file)) {
              existing.files.push(file);
            }
            for (const imp of dep.imports) {
              if (!existing.imports.includes(imp)) {
                existing.imports.push(imp);
              }
            }
          }
        } catch (error) {
          result.errors?.push(`Error analyzing ${file}: ${error}`);
        }
      }
    }

    result.detectedDependencies = Array.from(dependencyMap.values());
    return result;
  }

  /**
   * Determine search paths based on component type
   */
  private getSearchPaths(
    outputDir: string,
    componentName: string,
    componentType: string
  ): string[] {
    const paths: string[] = [];

    if (componentType === 'backend' || componentType === 'api') {
      // Backend components: search in component's own directory
      paths.push(path.join(outputDir, componentName, 'src'));
      paths.push(path.join(outputDir, componentName, 'src', 'routes'));
      paths.push(path.join(outputDir, componentName, 'src', 'controllers'));
      paths.push(path.join(outputDir, componentName, 'src', 'services'));

      // Also search in shared backend/src/services for infrastructure service clients
      paths.push(path.join(outputDir, 'backend', 'src', 'services'));
    } else if (componentType === 'frontend') {
      // Frontend components: search in frontend/src
      paths.push(path.join(outputDir, componentName, 'src'));
      paths.push(path.join(outputDir, componentName, 'src', 'components'));
      paths.push(path.join(outputDir, componentName, 'src', 'services'));

      // Also search in shared backend/src/services (frontend might import from there)
      paths.push(path.join(outputDir, 'backend', 'src', 'services'));
    }

    return paths;
  }

  /**
   * Find all TypeScript files in a directory (recursive)
   */
  private findTypeScriptFiles(dir: string): string[] {
    const files: string[] = [];

    if (!fs.existsSync(dir)) {
      return files;
    }

    const entries = fs.readdirSync(dir, { withFileTypes: true });

    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name);

      if (entry.isDirectory()) {
        files.push(...this.findTypeScriptFiles(fullPath));
      } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
        // Exclude .d.ts files and test files
        if (!entry.name.endsWith('.d.ts') && !entry.name.includes('.test.') && !entry.name.includes('.spec.')) {
          files.push(fullPath);
        }
      }
    }

    return files;
  }

  /**
   * Analyze a single TypeScript file for service dependencies
   */
  private analyzeFile(filePath: string): DetectedDependency[] {
    const dependencies: DetectedDependency[] = [];

    const sourceCode = fs.readFileSync(filePath, 'utf-8');
    const sourceFile = ts.createSourceFile(
      filePath,
      sourceCode,
      ts.ScriptTarget.Latest,
      true
    );

    // Visit all nodes in the AST
    const visit = (node: ts.Node) => {
      // Look for import declarations
      if (ts.isImportDeclaration(node)) {
        const importPath = (node.moduleSpecifier as ts.StringLiteral).text;

        // Check if this is a service client import
        if (importPath.includes('-client') || importPath.includes('/services/')) {
          const serviceName = this.extractServiceName(importPath);
          if (serviceName) {
            dependencies.push({
              service: serviceName,
              files: [filePath],
              imports: [importPath],
            });
          }
        }
      }

      // Look for require statements (for CommonJS)
      if (ts.isCallExpression(node)) {
        if (node.expression.kind === ts.SyntaxKind.Identifier) {
          const identifier = node.expression as ts.Identifier;
          if (identifier.text === 'require' && node.arguments.length > 0) {
            const arg = node.arguments[0];
            if (ts.isStringLiteral(arg)) {
              const requirePath = arg.text;
              if (requirePath.includes('-client') || requirePath.includes('/services/')) {
                const serviceName = this.extractServiceName(requirePath);
                if (serviceName) {
                  dependencies.push({
                    service: serviceName,
                    files: [filePath],
                    imports: [requirePath],
                  });
                }
              }
            }
          }
        }
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);

    return dependencies;
  }

  /**
   * Extract service name from import path
   * Examples:
   *   './services/redis-client' -> 'redis'
   *   '../services/queue-client' -> 'queue'
   *   './redis-client.ts' -> 'redis'
   */
  private extractServiceName(importPath: string): string | null {
    // Match patterns like 'redis-client', 'queue-client', etc.
    const match = importPath.match(/([a-z]+)-client/);
    if (match) {
      return match[1];
    }

    // If no match, try to extract from filename
    const basename = path.basename(importPath, '.ts');
    if (basename.endsWith('-client')) {
      return basename.replace('-client', '');
    }

    return null;
  }

  /**
   * Compare detected dependencies against expected dependencies
   */
  compareDependencies(
    expected: Array<{ service: string; usage: string; required?: boolean }>,
    detected: DetectedDependency[]
  ): string[] {
    const warnings: string[] = [];

    const expectedServices = new Set(expected.map(e => e.service));
    const detectedServices = new Set(detected.map(d => d.service));

    // Check for unexpected dependencies (detected but not expected)
    for (const service of detectedServices) {
      if (!expectedServices.has(service)) {
        const dep = detected.find(d => d.service === service)!;
        warnings.push(
          `Unexpected dependency on '${service}' found in: ${dep.files.map(f => path.basename(f)).join(', ')}`
        );
      }
    }

    // Check for missing dependencies (expected but not detected)
    for (const service of expectedServices) {
      if (!detectedServices.has(service)) {
        const exp = expected.find(e => e.service === service)!;
        if (exp.required) {
          warnings.push(
            `Missing required dependency '${service}' (declared for: ${exp.usage})`
          );
        } else {
          warnings.push(
            `Declared dependency '${service}' not found in code (declared for: ${exp.usage})`
          );
        }
      }
    }

    return warnings;
  }
}
