/**
 * Version Management CLI Commands
 * Handles version creation, rollback, and listing
 */

import { Command } from 'commander';
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
import { Git, GitError } from '../utils/git';
import { VersionManifest } from '../generators/manifest-generator';
import * as readline from 'readline';

/**
 * Prompt user for confirmation
 */
function confirm(question: string): Promise<boolean> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  return new Promise((resolve) => {
    rl.question(`${question} [y/N]: `, (answer) => {
      rl.close();
      resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
    });
  });
}

/**
 * Version Create Command
 * Creates a new version tag after validating the workflow
 */
async function versionCreate(version: string, options: any) {
  const git = new Git();

  console.log(`\n📦 Creating version ${version}...\n`);

  // Validate git repo
  if (!git.isGitRepo()) {
    console.error('❌ Error: Not a git repository');
    console.error('   Initialize git first: git init');
    process.exit(1);
  }

  // Validate semver
  if (!Git.isValidSemver(version)) {
    console.error(`❌ Error: Invalid semver format: ${version}`);
    console.error('   Use format: MAJOR.MINOR.PATCH (e.g., 1.0.0)');
    process.exit(1);
  }

  // Check if repository is clean
  const status = git.getStatus();
  if (!status.isClean && !options.force) {
    console.error('❌ Error: Repository has uncommitted changes');
    console.error('\nUncommitted files:');
    status.uncommittedChanges.forEach(file => console.error(`   ${file}`));
    console.error('\nCommit your changes first, or use --force to ignore');
    process.exit(1);
  }

  // Check if tag already exists
  const existingTag = git.getTag(version);
  if (existingTag) {
    console.error(`❌ Error: Version ${version} already exists`);
    console.error(`   Commit: ${existingTag.commit}`);
    console.error(`   Date: ${existingTag.date}`);
    process.exit(1);
  }

  // Find workflow file
  const workflowFile = options.workflow || findWorkflowFile();
  if (!workflowFile) {
    console.error('❌ Error: No workflow file found');
    console.error('   Specify with --workflow <path>');
    process.exit(1);
  }

  // Read and validate workflow
  const workflowContent = fs.readFileSync(workflowFile, 'utf-8');
  const workflow = yaml.load(workflowContent) as any;

  // Update workflow version
  workflow.workflow.version = version;
  fs.writeFileSync(workflowFile, yaml.dump(workflow, { lineWidth: -1 }));

  console.log(`✓ Updated workflow version to ${version}`);

  // Commit changes if needed
  if (!status.isClean || options.commit) {
    try {
      git.commit(`chore: bump version to ${version}`);
      console.log('✓ Created commit');
    } catch (error) {
      if (error instanceof GitError && error.message.includes('No changes')) {
        // No changes to commit, that's ok
      } else {
        throw error;
      }
    }
  }

  // Create tag
  try {
    git.createTag(version, `Release version ${version}`);
    console.log(`✓ Created tag: ${version}`);
  } catch (error) {
    if (error instanceof GitError) {
      console.error(`❌ Error: ${error.message}`);
      process.exit(1);
    }
    throw error;
  }

  console.log(`\n✅ Version ${version} created successfully!`);
  console.log(`   Push with: git push origin ${version}`);
}

/**
 * Version Rollback Command
 * Rolls back to a previous version with safety checks
 */
async function versionRollback(version: string, options: any) {
  const git = new Git();

  console.log(`\n🔄 Rolling back to version ${version}...\n`);

  // Validate git repo
  if (!git.isGitRepo()) {
    console.error('❌ Error: Not a git repository');
    process.exit(1);
  }

  // Check if repository is clean
  const status = git.getStatus();
  if (!status.isClean && !options.force) {
    console.error('❌ Error: Repository has uncommitted changes');
    console.error('\nUncommitted files:');
    status.uncommittedChanges.forEach(file => console.error(`   ${file}`));
    console.error('\nCommit or stash your changes first, or use --force to ignore');
    process.exit(1);
  }

  // Check if tag exists
  const tag = git.getTag(version);
  if (!tag) {
    console.error(`❌ Error: Version ${version} does not exist`);
    console.error('\nAvailable versions:');
    const tags = git.listTags();
    tags.slice(0, 5).forEach(t => console.error(`   ${t.tag} (${t.date})`));
    if (tags.length > 5) {
      console.error(`   ... and ${tags.length - 5} more`);
    }
    process.exit(1);
  }

  // Find current output directory with manifest
  const outputDir = options.output || process.cwd();
  const manifestPath = path.join(outputDir, '.bangstack', 'manifest.json');

  // Read current manifest if it exists
  let currentManifest: VersionManifest | null = null;
  if (fs.existsSync(manifestPath)) {
    currentManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
  }

  // Read manifest from target version
  const oldManifestContent = git.getFileAtTag(version, '.bangstack/manifest.json');
  let oldManifest: VersionManifest | null = null;
  if (oldManifestContent) {
    try {
      oldManifest = JSON.parse(oldManifestContent);
    } catch {
      console.warn('⚠️  Warning: Could not read manifest from version ' + version);
    }
  }

  // Compare manifests and show warnings
  if (currentManifest && oldManifest) {
    console.log('📊 Analyzing changes...\n');

    const warnings: string[] = [];

    // Check for service changes
    const currentServices = Object.keys(currentManifest.infrastructure_services);
    const oldServices = Object.keys(oldManifest.infrastructure_services);

    const removedServices = currentServices.filter(s => !oldServices.includes(s));
    const addedServices = oldServices.filter(s => !currentServices.includes(s));

    if (removedServices.length > 0) {
      warnings.push(`Services will be removed: ${removedServices.join(', ')}`);
      warnings.push(`→ Client files may exist: ${removedServices.map(s => `${s}-client.ts`).join(', ')}`);
    }

    if (addedServices.length > 0) {
      warnings.push(`Services will be added: ${addedServices.join(', ')}`);
    }

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

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

        const mismatched = currentDeps.filter(d => !oldDeps.includes(d));

        if (mismatched.length > 0) {
          warnings.push(
            `Component '${componentName}' code uses: [${currentDeps.join(', ')}] but v${version} expects: [${oldDeps.join(', ')}]`
          );
        }
      }
    }

    if (warnings.length > 0) {
      console.log('⚠️  Warnings:\n');
      warnings.forEach(w => console.log(`   ${w}`));
      console.log();
    }
  }

  // Show diff
  console.log('📝 Changes from current state:\n');
  try {
    const currentTag = git.getLatestVersion() || 'HEAD';
    const diff = git.diff(version, currentTag);
    console.log(diff);
    console.log();
  } catch {
    console.log('   (Could not generate diff)\n');
  }

  // Confirm rollback
  if (!options.yes) {
    const confirmed = await confirm(`Rollback to version ${version}?`);
    if (!confirmed) {
      console.log('\n❌ Rollback cancelled');
      process.exit(0);
    }
  }

  // Perform rollback
  try {
    git.checkout(version);
    console.log(`\n✅ Rolled back to version ${version}`);
    console.log(`   You are now in detached HEAD state at tag ${version}`);
    console.log(`   To continue working, create a new branch:`);
    console.log(`     git checkout -b rollback-${version}`);
  } catch (error) {
    if (error instanceof GitError) {
      console.error(`\n❌ Error: ${error.message}`);
      process.exit(1);
    }
    throw error;
  }
}

/**
 * Version List Command
 * Lists all available versions
 */
function versionList(_options: any) {
  const git = new Git();

  if (!git.isGitRepo()) {
    console.error('❌ Error: Not a git repository');
    process.exit(1);
  }

  const tags = git.listTags();
  const versionTags = tags.filter(t => Git.isValidSemver(t.tag));

  if (versionTags.length === 0) {
    console.log('No versions found');
    console.log('Create one with: bang version create <version>');
    return;
  }

  console.log('\n📦 Available versions:\n');

  versionTags.forEach((tag, index) => {
    const marker = index === 0 ? '→' : ' ';
    console.log(`${marker} ${tag.tag.padEnd(12)} ${tag.date}  ${tag.commit}  ${tag.message}`);
  });

  console.log();
}

/**
 * Version Diff Command
 * Shows differences between two versions
 */
function versionDiff(fromVersion: string, toVersion: string, _options: any) {
  const git = new Git();

  if (!git.isGitRepo()) {
    console.error('❌ Error: Not a git repository');
    process.exit(1);
  }

  // Validate both tags exist
  const fromTag = git.getTag(fromVersion);
  const toTag = git.getTag(toVersion);

  if (!fromTag) {
    console.error(`❌ Error: Version ${fromVersion} does not exist`);
    process.exit(1);
  }

  if (!toTag) {
    console.error(`❌ Error: Version ${toVersion} does not exist`);
    process.exit(1);
  }

  console.log(`\n📊 Comparing ${fromVersion} → ${toVersion}\n`);

  // Get manifests from both versions
  const fromManifestContent = git.getFileAtTag(fromVersion, '.bangstack/manifest.json');
  const toManifestContent = git.getFileAtTag(toVersion, '.bangstack/manifest.json');

  if (fromManifestContent && toManifestContent) {
    try {
      const fromManifest: VersionManifest = JSON.parse(fromManifestContent);
      const toManifest: VersionManifest = JSON.parse(toManifestContent);

      // Compare infrastructure services
      const fromServices = Object.keys(fromManifest.infrastructure_services);
      const toServices = Object.keys(toManifest.infrastructure_services);

      const addedServices = toServices.filter(s => !fromServices.includes(s));
      const removedServices = fromServices.filter(s => !toServices.includes(s));

      if (addedServices.length > 0 || removedServices.length > 0) {
        console.log('Infrastructure Services:');
        if (addedServices.length > 0) {
          console.log(`  + Added: ${addedServices.join(', ')}`);
        }
        if (removedServices.length > 0) {
          console.log(`  - Removed: ${removedServices.join(', ')}`);
        }
        console.log();
      }

      // Compare component dependencies
      console.log('Component Dependencies:');
      for (const [componentName, toComponent] of Object.entries(toManifest.components)) {
        const fromComponent = fromManifest.components[componentName];

        if (fromComponent) {
          const fromDeps = fromComponent.expected_dependencies?.map(d => d.service) || [];
          const toDeps = toComponent.expected_dependencies?.map(d => d.service) || [];

          const added = toDeps.filter(d => !fromDeps.includes(d));
          const removed = fromDeps.filter(d => !toDeps.includes(d));

          if (added.length > 0 || removed.length > 0) {
            console.log(`  ${componentName}:`);
            if (added.length > 0) {
              console.log(`    + ${added.join(', ')}`);
            }
            if (removed.length > 0) {
              console.log(`    - ${removed.join(', ')}`);
            }
          }
        } else {
          console.log(`  ${componentName}: (new component)`);
        }
      }
      console.log();
    } catch (_error) {
      console.warn('⚠️  Could not parse manifests\n');
    }
  }

  // Show git diff
  console.log('File Changes:');
  const diff = git.diff(fromVersion, toVersion);
  console.log(diff);
  console.log();
}

/**
 * Find workflow file in current directory
 */
function findWorkflowFile(): string | null {
  const cwd = process.cwd();
  const _patterns = ['*.workflow.yaml', '*.workflow.yml'];

  for (const _pattern of _patterns) {
    const files = fs.readdirSync(cwd)
      .filter(f => f.endsWith('.workflow.yaml') || f.endsWith('.workflow.yml'));

    if (files.length > 0) {
      return path.join(cwd, files[0]);
    }
  }

  return null;
}

/**
 * Register version commands
 */
export function registerVersionCommands(program: Command) {
  const version = program
    .command('version')
    .description('Manage workflow versions');

  // version create
  version
    .command('create <version>')
    .description('Create a new version tag')
    .option('-w, --workflow <path>', 'Path to workflow file')
    .option('-f, --force', 'Force creation even with uncommitted changes')
    .option('-c, --commit', 'Create a commit')
    .action(versionCreate);

  // version rollback
  version
    .command('rollback <version>')
    .description('Rollback to a previous version')
    .option('-o, --output <path>', 'Output directory with manifest', process.cwd())
    .option('-f, --force', 'Force rollback even with uncommitted changes')
    .option('-y, --yes', 'Skip confirmation prompt')
    .action(versionRollback);

  // version list
  version
    .command('list')
    .description('List all versions')
    .action(versionList);

  // version diff
  version
    .command('diff <from> <to>')
    .description('Compare two versions')
    .action(versionDiff);
}
