/**
 * Tests for ManifestGenerator
 */

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { ManifestGenerator, VersionManifest } from './manifest-generator';
import { GeneratorContext } from './types';

describe('ManifestGenerator', () => {
  let generator: ManifestGenerator;
  let testDir: string;

  beforeEach(() => {
    generator = new ManifestGenerator();
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'manifest-test-'));
  });

  afterEach(() => {
    if (fs.existsSync(testDir)) {
      fs.rmSync(testDir, { recursive: true, force: true });
    }
  });

  describe('generate', () => {
    it('should generate a manifest file', () => {
      const workflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test Workflow',
          description: 'Test Workflow',
        },
        stack: {
          api: {
            type: 'backend',
            technology: 'typescript/express',
          },
        },
        stages: {
          local: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: {
                  provider: 'local',
                  connection: 'redis://localhost:6379',
                },
              },
            },
          },
        },
      };

      const context: GeneratorContext = {
        workflow,
        executionPlan: { workflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);

      expect(result.files).toHaveLength(1);
      expect(result.files[0].path).toContain('manifest.json');
      expect(result.files[0].overwrite).toBe(true);

      // Parse and verify manifest content
      const manifest: VersionManifest = JSON.parse(result.files[0].content);
      expect(manifest.workflow_version).toBe('1.0.0');
      expect(manifest.generated_at).toBeDefined();
      expect(manifest.components.api).toBeDefined();
      expect(manifest.components.api.type).toBe('backend');
      expect(manifest.infrastructure_services.redis).toBeDefined();
    });

    it('should include component expected dependencies', () => {
      const workflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {
          api: {
            type: 'backend',
            technology: 'typescript/express',
            expected_dependencies: [
              { service: 'redis', usage: 'Caching' },
              { service: 'queue', usage: 'Jobs' },
            ],
          },
        },
      };

      const context: GeneratorContext = {
        workflow,
        executionPlan: { workflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      expect(manifest.components.api.expected_dependencies).toHaveLength(2);
      expect(manifest.components.api.expected_dependencies).toContainEqual({
        service: 'redis',
        usage: 'Caching',
      });
    });

    it('should collect services from multiple stages', () => {
      const workflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {},
        stages: {
          local: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: {
                  provider: 'local',
                  connection: 'redis://localhost:6379',
                },
              },
            },
          },
          production: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: {
                  provider: 'upstash',
                  connection: 'redis://upstash.example.com',
                },
                queue: {
                  provider: 'sqs',
                  connection: 'sqs://queue.amazonaws.com',
                },
              },
            },
          },
        },
      };

      const context: GeneratorContext = {
        workflow,
        executionPlan: { workflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      expect(Object.keys(manifest.infrastructure_services)).toContain('redis');
      expect(Object.keys(manifest.infrastructure_services)).toContain('queue');

      // Redis should exist in both stages
      expect(manifest.infrastructure_services.redis.stages).toContain('local');
      expect(manifest.infrastructure_services.redis.stages).toContain('production');

      // Queue only in production
      expect(manifest.infrastructure_services.queue.stages).toContain('production');
    });
  });

  describe('checkForMismatches', () => {
    it('should warn about version changes', () => {
      const oldManifest: VersionManifest = {
        workflow_version: '1.0.0',
        generated_at: new Date().toISOString(),
        components: {},
        infrastructure_services: {},
      };

      const newWorkflow = {
        workflow: {
          id: 'test',
          version: '2.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {},
      };

      // Write old manifest
      const manifestDir = path.join(testDir, '.bangstack');
      fs.mkdirSync(manifestDir, { recursive: true });
      fs.writeFileSync(
        path.join(manifestDir, 'manifest.json'),
        JSON.stringify(oldManifest, null, 2)
      );

      const context: GeneratorContext = {
        workflow: newWorkflow,
        executionPlan: { workflow: newWorkflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      expect(manifest.warnings).toBeDefined();
      const versionWarning = manifest.warnings?.find(w => w.includes('version changed'));
      expect(versionWarning).toContain('1.0.0');
      expect(versionWarning).toContain('2.0.0');
    });

    it('should warn about removed services', () => {
      const oldManifest: VersionManifest = {
        workflow_version: '1.0.0',
        generated_at: new Date().toISOString(),
        components: {},
        infrastructure_services: {
          redis: { provider: 'local', stages: ['local'] },
          queue: { provider: 'local', stages: ['local'] },
        },
      };

      const newWorkflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {},
        stages: {
          local: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: { provider: 'local', connection: 'redis://localhost:6379' },
                // queue removed
              },
            },
          },
        },
      };

      // Write old manifest
      const manifestDir = path.join(testDir, '.bangstack');
      fs.mkdirSync(manifestDir, { recursive: true });
      fs.writeFileSync(
        path.join(manifestDir, 'manifest.json'),
        JSON.stringify(oldManifest, null, 2)
      );

      const context: GeneratorContext = {
        workflow: newWorkflow,
        executionPlan: { workflow: newWorkflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      expect(manifest.warnings).toBeDefined();
      const removedWarning = manifest.warnings?.find(w => w.includes('Removed services'));
      expect(removedWarning).toContain('queue');
      expect(removedWarning).toContain('Client files may still exist');
    });

    it('should warn about added services', () => {
      const oldManifest: VersionManifest = {
        workflow_version: '1.0.0',
        generated_at: new Date().toISOString(),
        components: {},
        infrastructure_services: {
          redis: { provider: 'local', stages: ['local'] },
        },
      };

      const newWorkflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {},
        stages: {
          local: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: { provider: 'local', connection: 'redis://localhost:6379' },
                queue: { provider: 'local', connection: 'bullmq://localhost:6379' }, // Added
              },
            },
          },
        },
      };

      // Write old manifest
      const manifestDir = path.join(testDir, '.bangstack');
      fs.mkdirSync(manifestDir, { recursive: true });
      fs.writeFileSync(
        path.join(manifestDir, 'manifest.json'),
        JSON.stringify(oldManifest, null, 2)
      );

      const context: GeneratorContext = {
        workflow: newWorkflow,
        executionPlan: { workflow: newWorkflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      expect(manifest.warnings).toBeDefined();
      const addedWarning = manifest.warnings?.find(w => w.includes('Added services'));
      expect(addedWarning).toContain('queue');
    });

    it('should not warn when no changes', () => {
      const oldManifest: VersionManifest = {
        workflow_version: '1.0.0',
        generated_at: new Date().toISOString(),
        components: {},
        infrastructure_services: {
          redis: { provider: 'local', stages: ['local'] },
        },
      };

      const newWorkflow = {
        workflow: {
          id: 'test',
          version: '1.0.0', // Same version
          name: 'Test',
          description: 'Test',
        },
        stack: {},
        stages: {
          local: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: { provider: 'local', connection: 'redis://localhost:6379' }, // Same service
              },
            },
          },
        },
      };

      // Write old manifest
      const manifestDir = path.join(testDir, '.bangstack');
      fs.mkdirSync(manifestDir, { recursive: true });
      fs.writeFileSync(
        path.join(manifestDir, 'manifest.json'),
        JSON.stringify(oldManifest, null, 2)
      );

      const context: GeneratorContext = {
        workflow: newWorkflow,
        executionPlan: { workflow: newWorkflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      // No warnings about version or service changes
      expect(manifest.warnings).toBeUndefined();
    });
  });

  describe('scanImplementationFiles', () => {
    it('should detect and checksum client files', () => {
      // Create a backend services directory with client files
      const servicesDir = path.join(testDir, 'backend', 'src', 'services');
      fs.mkdirSync(servicesDir, { recursive: true });

      fs.writeFileSync(
        path.join(servicesDir, 'redis-client.ts'),
        'export const redisClient = {};'
      );

      fs.writeFileSync(
        path.join(servicesDir, 'queue-client.ts'),
        'export const queueClient = {};'
      );

      const workflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {
          api: {
            type: 'backend',
            technology: 'typescript/express',
          },
        },
      };

      const context: GeneratorContext = {
        workflow,
        executionPlan: { workflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      const apiFiles = manifest.components.api.implementation_files;
      expect(apiFiles['redis-client.ts']).toBeDefined();
      expect(apiFiles['queue-client.ts']).toBeDefined();

      expect(apiFiles['redis-client.ts'].checksum).toHaveLength(64); // SHA-256
      expect(apiFiles['redis-client.ts'].overwrite).toBe(false);
      expect(apiFiles['redis-client.ts'].last_generated).toBeDefined();
    });

    it('should detect file modifications via checksum', () => {
      const servicesDir = path.join(testDir, 'backend', 'src', 'services');
      fs.mkdirSync(servicesDir, { recursive: true });

      const originalContent = 'export const redisClient = {};';
      fs.writeFileSync(
        path.join(servicesDir, 'redis-client.ts'),
        originalContent
      );

      // Generate first manifest
      const workflow = {
        workflow: {
          id: 'test',
          version: '1.0.0',
          name: 'Test',
          description: 'Test',
        },
        stack: {
          api: {
            type: 'backend',
            technology: 'typescript/express',
          },
        },
      };

      const context: GeneratorContext = {
        workflow,
        executionPlan: { workflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result1 = generator.generate(context);
      const _manifest1: VersionManifest = JSON.parse(result1.files[0].content);

      // Write manifest to disk
      const manifestDir = path.join(testDir, '.bangstack');
      fs.mkdirSync(manifestDir, { recursive: true });
      fs.writeFileSync(result1.files[0].path, result1.files[0].content);

      // Modify the file
      const modifiedContent = 'export const redisClient = { modified: true };';
      fs.writeFileSync(
        path.join(servicesDir, 'redis-client.ts'),
        modifiedContent
      );

      // Generate second manifest
      const result2 = generator.generate(context);
      const manifest2: VersionManifest = JSON.parse(result2.files[0].content);

      // Should warn about file modification
      expect(manifest2.warnings).toBeDefined();
      const modificationWarning = manifest2.warnings?.find(w =>
        w.includes('manually modified')
      );
      expect(modificationWarning).toContain('redis-client.ts');
      expect(modificationWarning).toContain('checksum changed');
    });
  });

  describe('integration scenarios', () => {
    it('should handle complete workflow with dependencies', () => {
      const workflow = {
        workflow: {
          id: 'complete-test',
          version: '1.0.0',
          name: 'Complete Test',
          description: 'Complete integration test',
        },
        stack: {
          api: {
            type: 'backend',
            technology: 'typescript/express',
            expected_dependencies: [
              { service: 'redis', usage: 'Session storage', required: true },
              { service: 'queue', usage: 'Background jobs', required: true },
            ],
          },
          cache: {
            type: 'backend',
            technology: 'typescript/express',
            expected_dependencies: [
              { service: 'redis', usage: 'Caching' },
            ],
          },
        },
        stages: {
          local: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: {
                  provider: 'local',
                  connection: 'redis://localhost:6379',
                },
                queue: {
                  provider: 'local',
                  connection: 'bullmq://localhost:6379',
                },
              },
            },
          },
          production: {
            infrastructure: {
              databases: { primary: "postgresql://localhost:5432/test" },
              services: {
                redis: {
                  provider: 'upstash',
                  connection: '{{secrets.REDIS_URL}}',
                },
                queue: {
                  provider: 'aws-sqs',
                  connection: '{{secrets.QUEUE_URL}}',
                },
              },
            },
          },
        },
      };

      const context: GeneratorContext = {
        workflow,
        executionPlan: { workflow, componentPlans: new Map() },
        outputDirectory: testDir,
      };

      const result = generator.generate(context);
      const manifest: VersionManifest = JSON.parse(result.files[0].content);

      // Verify manifest structure
      expect(manifest.workflow_version).toBe('1.0.0');
      expect(Object.keys(manifest.components)).toHaveLength(2);
      expect(Object.keys(manifest.infrastructure_services)).toHaveLength(2);

      // Verify components
      expect(manifest.components.api.expected_dependencies).toHaveLength(2);
      expect(manifest.components.cache.expected_dependencies).toHaveLength(1);

      // Verify infrastructure services span multiple stages
      expect(manifest.infrastructure_services.redis.stages).toContain('local');
      expect(manifest.infrastructure_services.redis.stages).toContain('production');
      expect(manifest.infrastructure_services.queue.stages).toContain('local');
      expect(manifest.infrastructure_services.queue.stages).toContain('production');
    });
  });
});
