/**
 * Manifest Generator
 * Tracks workflow versions, component dependencies, and file checksums
 * Enables version rollback detection and mismatch warnings
 */

import { Generator, GeneratorContext, GenerationResult, GeneratedFile } from './types';
import { DependencyAnalyzer, DetectedDependency } from './dependency-analyzer';
import * as path from 'path';
import * as crypto from 'crypto';
import * as fs from 'fs';

export interface VersionManifest {
  workflow_version: string;
  generated_at: string;
  components: Record<string, ComponentManifest>;
  infrastructure_services: Record<string, ServiceManifest>;
  warnings?: string[];
}

export interface ComponentManifest {
  type: string;
  technology: string;
  expected_dependencies?: Array<{
    service: string;
    usage: string;
    required?: boolean;
  }>;
  detected_dependencies?: DetectedDependency[]; // Actually detected from code analysis
  implementation_files: Record<string, FileInfo>;
}

export interface ServiceManifest {
  provider: string;
  stages: string[]; // Which stages this service exists in
}

export interface FileInfo {
  checksum: string;
  overwrite: boolean;
  last_generated: string;
}

export class ManifestGenerator implements Generator {
  id = 'version-manifest';
  name = 'Version Manifest Generator';
  description = 'Generates version tracking manifest for rollback detection';

  private analyzer: DependencyAnalyzer;

  constructor() {
    this.analyzer = new DependencyAnalyzer();
  }

  generate(context: GeneratorContext): GenerationResult {
    const { workflow, outputDirectory } = context;
    const files: GeneratedFile[] = [];

    // Generate manifest (includes dependency analysis warnings)
    const manifest = this.createManifest(workflow, outputDirectory);

    // Check for existing manifest and compare (version changes, service changes, etc.)
    const manifestPath = path.join(outputDirectory, '.bangstack', 'manifest.json');
    const comparisonWarnings = this.checkForMismatches(manifestPath, manifest);

    // Merge warnings from dependency analysis and version comparison
    const allWarnings = [
      ...(manifest.warnings || []),
      ...comparisonWarnings,
    ];

    if (allWarnings.length > 0) {
      manifest.warnings = allWarnings;
    }

    // Write manifest
    files.push({
      path: manifestPath,
      content: JSON.stringify(manifest, null, 2),
      overwrite: true, // Always overwrite manifest
    });

    return { files };
  }

  private createManifest(workflow: any, outputDir: string): VersionManifest {
    const manifest: VersionManifest = {
      workflow_version: workflow.workflow.version,
      generated_at: new Date().toISOString(),
      components: {},
      infrastructure_services: {},
    };

    const warnings: string[] = [];

    // Collect component information
    for (const [componentName, component] of Object.entries(workflow.stack || {})) {
      const comp = component as any;

      // Analyze component code for actual dependencies
      const analysisResult = this.analyzer.analyzeComponent(
        outputDir,
        componentName,
        comp.type,
        comp.technology
      );

      manifest.components[componentName] = {
        type: comp.type,
        technology: comp.technology,
        expected_dependencies: comp.expected_dependencies || [],
        detected_dependencies: analysisResult.detectedDependencies,
        implementation_files: this.scanImplementationFiles(outputDir, componentName),
      };

      // Add analysis errors/warnings if any (e.g., unsupported language)
      if (analysisResult.errors && analysisResult.errors.length > 0) {
        for (const error of analysisResult.errors) {
          // Check if this is an unsupported language message
          const isUnsupportedLanguage = error.includes('not supported');

          if (isUnsupportedLanguage) {
            // Don't treat as error, just informational warning
            warnings.push(`Component '${componentName}': ${error}`);
          } else {
            // Real error during analysis
            warnings.push(`Component '${componentName}': ${error}`);
          }
        }
      }

      // Compare expected vs detected dependencies (only if analysis was successful)
      const analysisSuccessful = !analysisResult.errors ||
                                  !analysisResult.errors.some(e => e.includes('not supported'));

      if (analysisSuccessful &&
          comp.expected_dependencies &&
          comp.expected_dependencies.length > 0) {
        const comparisonWarnings = this.analyzer.compareDependencies(
          comp.expected_dependencies,
          analysisResult.detectedDependencies
        );

        for (const warning of comparisonWarnings) {
          warnings.push(`Component '${componentName}': ${warning}`);
        }
      }
    }

    // Collect infrastructure services across all stages
    if (workflow.stages) {
      const allServices = this.collectInfrastructureServices(workflow);

      for (const [serviceName, serviceData] of Object.entries(allServices)) {
        const data = serviceData as any;
        manifest.infrastructure_services[serviceName] = {
          provider: data.providers.join(', '),
          stages: data.stages,
        };
      }
    }

    // Store warnings from dependency analysis
    if (warnings.length > 0) {
      manifest.warnings = warnings;
    }

    return manifest;
  }

  private collectInfrastructureServices(workflow: any): Record<string, any> {
    const services: Record<string, any> = {};

    for (const [stageName, stage] of Object.entries(workflow.stages || {})) {
      const stageConfig = stage as any;
      const infraServices = stageConfig.infrastructure?.services || {};

      for (const [serviceName, serviceConfig] of Object.entries(infraServices)) {
        const config = serviceConfig as any;

        if (!services[serviceName]) {
          services[serviceName] = {
            providers: [],
            stages: [],
          };
        }

        if (config.provider && !services[serviceName].providers.includes(config.provider)) {
          services[serviceName].providers.push(config.provider);
        }

        services[serviceName].stages.push(stageName);
      }
    }

    return services;
  }

  private scanImplementationFiles(outputDir: string, _componentName: string): Record<string, FileInfo> {
    const files: Record<string, FileInfo> = {};

    // Look for client files in backend/src/services/
    const servicesDir = path.join(outputDir, 'backend', 'src', 'services');

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

    const clientFiles = fs.readdirSync(servicesDir).filter(f => f.endsWith('-client.ts'));

    for (const file of clientFiles) {
      const filePath = path.join(servicesDir, file);
      const stats = fs.statSync(filePath);
      const content = fs.readFileSync(filePath, 'utf-8');
      const checksum = crypto.createHash('sha256').update(content).digest('hex');

      files[file] = {
        checksum,
        overwrite: false, // Client files are never overwritten
        last_generated: stats.mtime.toISOString(),
      };
    }

    return files;
  }

  private checkForMismatches(manifestPath: string, newManifest: VersionManifest): string[] {
    const warnings: string[] = [];

    // Check if previous manifest exists
    if (!fs.existsSync(manifestPath)) {
      return warnings; // First generation, no warnings
    }

    try {
      const oldContent = fs.readFileSync(manifestPath, 'utf-8');
      const oldManifest: VersionManifest = JSON.parse(oldContent);

      // Check for version changes
      if (oldManifest.workflow_version !== newManifest.workflow_version) {
        warnings.push(
          `Workflow version changed: ${oldManifest.workflow_version} → ${newManifest.workflow_version}`
        );
      }

      // Check for removed services
      const oldServices = Object.keys(oldManifest.infrastructure_services);
      const newServices = Object.keys(newManifest.infrastructure_services);

      const removedServices = oldServices.filter(s => !newServices.includes(s));
      if (removedServices.length > 0) {
        warnings.push(
          `Removed services: ${removedServices.join(', ')} - Client files may still exist`
        );
      }

      // Check for added services
      const addedServices = newServices.filter(s => !oldServices.includes(s));
      if (addedServices.length > 0) {
        warnings.push(
          `Added services: ${addedServices.join(', ')}`
        );
      }

      // Check for component dependency changes
      for (const [componentName, component] of Object.entries(newManifest.components)) {
        const oldComponent = oldManifest.components[componentName];

        if (oldComponent && component.expected_dependencies) {
          const oldDeps = oldComponent.expected_dependencies?.map(d => d.service) || [];
          const newDeps = component.expected_dependencies.map(d => d.service);

          const removedDeps = oldDeps.filter(d => !newDeps.includes(d));
          const addedDeps = newDeps.filter(d => !oldDeps.includes(d));

          if (removedDeps.length > 0 || addedDeps.length > 0) {
            warnings.push(
              `Component '${componentName}' dependencies changed: ` +
              `removed [${removedDeps.join(', ')}], added [${addedDeps.join(', ')}]`
            );
          }
        }
      }

      // Check for file modifications
      for (const [componentName, component] of Object.entries(newManifest.components)) {
        const oldComponent = oldManifest.components[componentName];

        if (oldComponent) {
          for (const [fileName, fileInfo] of Object.entries(component.implementation_files)) {
            const oldFileInfo = oldComponent.implementation_files[fileName];

            if (oldFileInfo && oldFileInfo.checksum !== fileInfo.checksum) {
              warnings.push(
                `File '${fileName}' was manually modified (checksum changed) - verify compatibility on rollback`
              );
            }
          }
        }
      }

    } catch (error) {
      warnings.push(`Warning: Could not read previous manifest: ${error}`);
    }

    return warnings;
  }
}
