/**
 * Integration tests for Version Commands
 */

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { execSync } from 'child_process';
import * as yaml from 'js-yaml';

describe('version commands integration', () => {
  let testDir: string;
  let cliPath: string;

  beforeEach(() => {
    testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'version-test-'));
    cliPath = path.join(__dirname, '../../dist/cli/index.js');

    // Initialize git repo
    execSync('git init', { cwd: testDir });
    execSync('git config user.email "test@example.com"', { cwd: testDir });
    execSync('git config user.name "Test User"', { cwd: testDir });
  });

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

  /**
   * Helper to create a workflow file
   */
  const createWorkflow = (version: string = '1.0.0'): string => {
    const workflow = {
      workflow: {
        id: 'test',
        version,
        name: 'Test Workflow',
        description: 'Test',
      },
      stack: {
        api: {
          type: 'backend',
          technology: 'typescript/express',
        },
      },
    };

    const workflowPath = path.join(testDir, 'test.workflow.yaml');
    fs.writeFileSync(workflowPath, yaml.dump(workflow));
    return workflowPath;
  };

  /**
   * Helper to create manifest
   */
  const createManifest = (version: string, services: string[] = []) => {
    const manifestDir = path.join(testDir, '.bangstack');
    fs.mkdirSync(manifestDir, { recursive: true });

    const manifest = {
      workflow_version: version,
      generated_at: new Date().toISOString(),
      components: {
        api: {
          type: 'backend',
          technology: 'typescript/express',
          expected_dependencies: [],
          implementation_files: {},
        },
      },
      infrastructure_services: services.reduce((acc, service) => {
        acc[service] = { provider: 'local', stages: ['local'] };
        return acc;
      }, {} as any),
    };

    fs.writeFileSync(
      path.join(manifestDir, 'manifest.json'),
      JSON.stringify(manifest, null, 2)
    );
  };

  /**
   * Helper to make initial commit
   */
  const makeInitialCommit = () => {
    const readme = path.join(testDir, 'README.md');
    fs.writeFileSync(readme, '# Test Project');
    execSync('git add .', { cwd: testDir });
    execSync('git commit -m "Initial commit"', { cwd: testDir });
  };

  /**
   * Helper to run version command
   */
  const runVersionCmd = (args: string, _expectSuccess = true): { output: string; exitCode: number } => {
    try {
      const output = execSync(
        `node "${cliPath}" version ${args}`,
        {
          cwd: testDir,
          encoding: 'utf-8',
          stdio: ['pipe', 'pipe', 'pipe']
        }
      );
      return { output, exitCode: 0 };
    } catch (error: any) {
      const output = [error.stdout, error.stderr].filter(Boolean).join('\n') || error.message;
      return {
        output,
        exitCode: error.status || 1
      };
    }
  };

  describe('version create', () => {
    it('should create a new version tag', () => {
      makeInitialCommit();
      createWorkflow('1.0.0');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });

      const { output, exitCode } = runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      expect(exitCode).toBe(0);
      expect(output).toContain('1.0.0');
      expect(output).toContain('created successfully');

      // Verify tag exists
      const tags = execSync('git tag', { cwd: testDir, encoding: 'utf-8' });
      expect(tags).toContain('1.0.0');
    });

    it('should update workflow version in file', () => {
      makeInitialCommit();
      createWorkflow('0.9.0');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });

      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Read workflow and check version was updated
      const workflowContent = fs.readFileSync(path.join(testDir, 'test.workflow.yaml'), 'utf-8');
      const workflow = yaml.load(workflowContent) as any;
      expect(workflow.workflow.version).toBe('1.0.0');
    });

    it('should reject invalid semver', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });

      const { output, exitCode } = runVersionCmd('create invalid', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('Invalid semver');
    });

    it('should reject if tag already exists', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });

      // Create first tag
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Try to create same tag again
      const { output, exitCode } = runVersionCmd('create 1.0.0 -w test.workflow.yaml', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('already exists');
    });

    it('should reject uncommitted changes without --force', () => {
      makeInitialCommit();
      createWorkflow();
      // Don't commit the workflow file

      const { output, exitCode } = runVersionCmd('create 1.0.0 -w test.workflow.yaml', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('uncommitted changes');
    });

    it('should allow uncommitted changes with --force', () => {
      makeInitialCommit();
      createWorkflow();
      // Don't commit the workflow file

      const { output, exitCode } = runVersionCmd('create 1.0.0 -w test.workflow.yaml --force');

      expect(exitCode).toBe(0);
      expect(output).toContain('created successfully');
    });
  });

  describe('version list', () => {
    it('should list all version tags', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });

      // Create multiple versions (need to commit between each)
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 1.0.0" --allow-empty', { cwd: testDir });

      runVersionCmd('create 1.1.0 -w test.workflow.yaml');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 1.1.0" --allow-empty', { cwd: testDir });

      runVersionCmd('create 2.0.0 -w test.workflow.yaml');

      const { output, exitCode } = runVersionCmd('list');

      expect(exitCode).toBe(0);
      expect(output).toContain('1.0.0');
      expect(output).toContain('1.1.0');
      expect(output).toContain('2.0.0');
    });

    it('should show message when no versions exist', () => {
      makeInitialCommit();

      const { output, exitCode } = runVersionCmd('list');

      expect(exitCode).toBe(0);
      expect(output).toContain('No versions found');
    });

    it('should ignore non-semver tags', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });

      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Create non-semver tag
      execSync('git tag release', { cwd: testDir });

      const { output, exitCode } = runVersionCmd('list');

      expect(exitCode).toBe(0);
      expect(output).toContain('1.0.0');
      expect(output).not.toContain('release');
    });
  });

  describe('version diff', () => {
    it('should show differences between two versions', () => {
      makeInitialCommit();
      createWorkflow();
      createManifest('1.0.0', ['redis']);
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 1.0.0"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Make changes
      createManifest('2.0.0', ['redis', 'queue']);
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 2.0.0"', { cwd: testDir });
      runVersionCmd('create 2.0.0 -w test.workflow.yaml');

      const { output, exitCode } = runVersionCmd('diff 1.0.0 2.0.0');

      expect(exitCode).toBe(0);
      expect(output).toContain('1.0.0');
      expect(output).toContain('2.0.0');
    });

    it('should error if from version does not exist', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      const { output, exitCode } = runVersionCmd('diff 0.9.0 1.0.0', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('does not exist');
    });

    it('should error if to version does not exist', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      const { output, exitCode } = runVersionCmd('diff 1.0.0 2.0.0', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('does not exist');
    });
  });

  describe('version rollback', () => {
    it('should rollback to previous version', () => {
      makeInitialCommit();
      createWorkflow('1.0.0');
      createManifest('1.0.0');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 1.0.0"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Make changes
      createWorkflow('2.0.0');
      createManifest('2.0.0');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 2.0.0"', { cwd: testDir });
      runVersionCmd('create 2.0.0 -w test.workflow.yaml');

      // Rollback with --yes to skip confirmation
      const { output, exitCode } = runVersionCmd('rollback 1.0.0 --yes');

      expect(exitCode).toBe(0);
      expect(output).toContain('Rolled back');
      expect(output).toContain('1.0.0');
    });

    it('should error if version does not exist', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      const { output, exitCode } = runVersionCmd('rollback 2.0.0 --yes', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('does not exist');
    });

    it('should error with uncommitted changes', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Make uncommitted changes
      fs.writeFileSync(path.join(testDir, 'uncommitted.txt'), 'uncommitted');

      const { output, exitCode } = runVersionCmd('rollback 1.0.0 --yes', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('uncommitted changes');
    });

    it('should still error with --force if git cannot checkout', () => {
      makeInitialCommit();
      createWorkflow();
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Add workflow"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Make another version
      createWorkflow('2.0.0');
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 2.0.0"', { cwd: testDir });
      runVersionCmd('create 2.0.0 -w test.workflow.yaml');

      // Make uncommitted changes
      fs.writeFileSync(path.join(testDir, 'uncommitted.txt'), 'uncommitted');

      // --force bypasses bangstack check but git still won't checkout
      const { output, exitCode } = runVersionCmd('rollback 1.0.0 --yes --force', false);

      expect(exitCode).toBe(1);
      expect(
        output.includes('uncommitted changes') || output.includes('Error')
      ).toBe(true);
    });

    it('should show warnings about service changes', () => {
      makeInitialCommit();
      createWorkflow('1.0.0');
      createManifest('1.0.0', ['redis']);
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 1.0.0"', { cwd: testDir });
      runVersionCmd('create 1.0.0 -w test.workflow.yaml');

      // Add service in new version
      createWorkflow('2.0.0');
      createManifest('2.0.0', ['redis', 'queue']);
      execSync('git add .', { cwd: testDir });
      execSync('git commit -m "Version 2.0.0"', { cwd: testDir });
      runVersionCmd('create 2.0.0 -w test.workflow.yaml');

      // Rollback to 1.0.0 (which doesn't have queue)
      const { output, exitCode } = runVersionCmd('rollback 1.0.0 --yes');

      expect(exitCode).toBe(0);
      expect(
        output.includes('Warning') || output.includes('will be removed')
      ).toBe(true);
    });
  });

  describe('error handling', () => {
    it('should error when not in git repo', () => {
      // Create a non-git directory
      const nonGitDir = fs.mkdtempSync(path.join(os.tmpdir(), 'non-git-'));

      try {
        const _output = execSync(
          `node "${cliPath}" version list`,
          { cwd: nonGitDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
        );
      } catch (error: any) {
        const output = [error.stdout, error.stderr].filter(Boolean).join('\n');
        expect(error.status).toBe(1);
        expect(output).toContain('Not a git repository');
      } finally {
        fs.rmSync(nonGitDir, { recursive: true, force: true });
      }
    });

    it('should error when workflow file not found', () => {
      makeInitialCommit();

      const { output, exitCode } = runVersionCmd('create 1.0.0', false);

      expect(exitCode).toBe(1);
      expect(output).toContain('No workflow file found');
    });
  });
});
