import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
import fs from 'fs-extra';
import ora from 'ora';
import chalk from 'chalk';
import { logger } from '../../utils/logger.js';
import {
  getContentDir,
  copyDirectory,
  pathExists,
  ensureDir,
  createDirectoryLink,
  readJson,
  writeJson
} from '../../utils/file-copier.js';
import {
  checkCLIOutdated,
  checkProjectOutdated,
  saveProjectMorphVersion,
  getUpdateInstructions,
  detectInstallMethod
} from '../../utils/version-checker.js';
import { installClaudeHooks } from '../../utils/hooks-installer.js';
import { AutoContextOrchestrator } from '../../core/orchestrator.js';
import { detectClaudeCode } from '../../llm/environment-detector.js';

export async function updateCommand(options) {
  const targetPath = process.cwd();
  const contentDir = getContentDir();
  const morphPath = join(targetPath, '.morph');

  logger.header('MORPH-SPEC Update');

  // Check if MORPH is initialized
  if (!(await pathExists(morphPath))) {
    logger.error('MORPH not initialized in this directory.');
    logger.dim('Run "morph-spec init" first.');
    process.exit(1);
  }

  // Check CLI version
  const spinner = ora('Checking versions...').start();
  const cliCheck = await checkCLIOutdated();
  const projectCheck = await checkProjectOutdated(targetPath);

  spinner.stop();

  // Display version info
  logger.blank();
  logger.info('Version Status:');
  logger.dim(`  CLI version: ${cliCheck.current}`);

  if (projectCheck.current) {
    logger.dim(`  Project MORPH version: ${projectCheck.current}`);
  } else {
    logger.dim('  Project MORPH version: not found (legacy installation)');
  }

  if (cliCheck.latest) {
    const latestColor = cliCheck.isOutdated ? chalk.yellow : chalk.green;
    logger.dim(`  Latest available: ${latestColor(cliCheck.latest)}`);
  }
  logger.blank();

  // If CLI is outdated, stop and instruct user
  if (cliCheck.isOutdated && cliCheck.latest) {
    logger.warn(`Your CLI is outdated (${cliCheck.current} → ${cliCheck.latest})`);
    logger.blank();
    logger.info('Please update the CLI first:');
    logger.blank();

    const method = detectInstallMethod();
    const instructions = getUpdateInstructions(method);

    instructions.forEach(line => {
      if (line === '') {
        logger.blank();
      } else if (line.startsWith('Or ')) {
        logger.dim(line);
      } else {
        logger.box([line]);
      }
    });

    logger.blank();
    logger.dim('Then run "morph-spec update" again.');
    process.exit(0);
  }

  // Proceed with update
  const updateSpinner = ora('Updating MORPH-SPEC...').start();

  try {
    const updateTemplates = !options.standards || options.templates;
    const updateStandards = !options.templates || options.standards;

    // Update templates
    if (updateTemplates) {
      updateSpinner.text = 'Updating templates...';
      const templatesSrc = join(contentDir, '.morph', 'templates');
      const templatesDest = join(morphPath, 'templates');
      await copyDirectory(templatesSrc, templatesDest);
    }

    // Update standards
    if (updateStandards) {
      updateSpinner.text = 'Updating standards...';
      const standardsSrc = join(contentDir, '.morph', 'standards');
      const standardsDest = join(morphPath, 'standards');
      await copyDirectory(standardsSrc, standardsDest);
    }

    // Update agents.json (sourced from framework root .morph/config/ — canonical single source of truth)
    updateSpinner.text = 'Updating agents configuration...';
    const agentsSrc = join(__dirname, '..', '..', '..', '.morph', 'config', 'agents.json');
    const agentsDest = join(morphPath, 'config', 'agents.json');
    if (await pathExists(agentsSrc)) {
      await copyDirectory(agentsSrc, agentsDest);
    }

    // Update .claude commands and skills
    // Source: framework root .claude/ (canonical for all stacks)
    updateSpinner.text = 'Updating Claude commands and skills...';
    const claudeSrc = join(__dirname, '..', '..', '..', '.claude');
    const claudeDest = join(targetPath, '.claude');
    let claudeUpdated = false;
    if (await pathExists(claudeSrc)) {
      // Copy commands (always copy, these are small)
      const commandsSrc = join(claudeSrc, 'commands');
      const commandsDest = join(claudeDest, 'commands');
      if (await pathExists(commandsSrc)) {
        await copyDirectory(commandsSrc, commandsDest);
        claudeUpdated = true;

        // Clean up stale command files moved to skills/workflows/ in v2.4+
        const staleCommands = [
          'morph-setup.md', 'morph-uiux.md', 'morph-design.md',
          'morph-clarify.md', 'morph-tasks.md'
        ];
        for (const file of staleCommands) {
          const stalePath = join(commandsDest, file);
          if (await pathExists(stalePath)) {
            await fs.remove(stalePath);
          }
        }
      } else {
        logger.warn('  ⚠ .claude/commands/ source missing — commands not updated');
      }

      // Update skills using directory links per category
      const skillsSrc = join(claudeSrc, 'skills');
      const skillsDest = join(claudeDest, 'skills');
      if (await pathExists(skillsSrc)) {
        await ensureDir(skillsDest);
        const entries = await fs.readdir(skillsSrc, { withFileTypes: true });
        for (const entry of entries) {
          if (entry.isDirectory()) {
            await createDirectoryLink(join(skillsSrc, entry.name), join(skillsDest, entry.name));
          }
        }
      }
    }

    // Update agent-teams hooks in .claude/settings.local.json
    updateSpinner.text = 'Updating Claude Code hooks...';
    await installClaudeHooks(targetPath);

    // Update CLAUDE.md
    updateSpinner.text = 'Updating CLAUDE.md...';
    const claudeMdSrc = join(contentDir, 'CLAUDE.md');
    const claudeMdDest = join(targetPath, 'CLAUDE.md');
    await copyDirectory(claudeMdSrc, claudeMdDest);

    // Update .morphversion
    updateSpinner.text = 'Saving version info...';
    await saveProjectMorphVersion(targetPath, cliCheck.current);

    // Update config.json frameworkVersion (BUG #9 fix)
    const configPath = join(morphPath, 'config', 'config.json');
    if (await pathExists(configPath)) {
      try {
        const config = await readJson(configPath);
        config.frameworkVersion = cliCheck.current;
        await writeJson(configPath, config);
      } catch { /* preserve existing config if read/write fails */ }
    }

    updateSpinner.succeed('MORPH-SPEC updated successfully!');
    logger.blank();

    // Show version change
    if (projectCheck.current && projectCheck.current !== cliCheck.current) {
      logger.success(`Updated: ${projectCheck.current} → ${cliCheck.current}`);
    } else if (!projectCheck.current) {
      logger.success(`Updated to v${cliCheck.current}`);
    } else {
      logger.success(`Already up to date (v${cliCheck.current})`);
    }

    logger.blank();
    logger.info('Updated files:');
    if (updateTemplates) logger.dim('  ✓ .morph/templates/');
    if (updateStandards) logger.dim('  ✓ .morph/standards/');
    logger.dim('  ✓ .morph/config/agents.json');
    if (claudeUpdated) logger.dim('  ✓ .claude/commands/ and .claude/skills/');
    logger.dim('  ✓ .claude/settings.local.json (agent-teams hooks)');
    logger.dim('  ✓ CLAUDE.md');
    logger.blank();
    logger.info('Your config.json was preserved.');
    logger.dim('Review the updated files for any new features.');
    logger.blank();

    // Re-analyze project context (unless --skip-detection)
    if (!options.skipDetection && detectClaudeCode()) {
      logger.header('Re-Analyzing Project Context');
      logger.dim('Using Claude Code LLM to update project detection...');
      logger.blank();

      try {
        const orchestrator = new AutoContextOrchestrator();
        const result = await orchestrator.execute(targetPath, {
          skipReview: false,
          fallbackOnError: true,
          wizardMode: options.wizard || false
        });

        if (result.success) {
          logger.success('Project context re-analyzed and updated');
          logger.dim('  ✓ .morph/project.md updated with latest detection');
          logger.dim('  ✓ .morph/config/config.json updated with stack info');
        } else {
          logger.warn('Context re-analysis incomplete');
        }
      } catch (error) {
        logger.warn(`Auto-detection failed: ${error.message}`);
        logger.dim('Your existing config.json was preserved.');
      }

      logger.blank();
    } else if (options.skipDetection) {
      logger.dim('Skipped auto-detection (--skip-detection flag)');
    } else {
      logger.warn('⚠️  Claude Code not detected - skipping auto-detection');
      logger.dim('Run with --wizard to configure manually.');
    }

  } catch (error) {
    updateSpinner.fail('Update failed');
    logger.error(error.message);
    process.exit(1);
  }
}
