import { join } from 'path';
import { execSync } from 'child_process';
import { platform } from 'os';
import fs from 'fs-extra';
import chalk from 'chalk';
import { logger } from '../../utils/logger.js';
import { pathExists, readJson } from '../../utils/file-copier.js';
import {
  checkCLIOutdated,
  checkProjectOutdated,
  getInstalledCLIVersion
} from '../../utils/version-checker.js';

const isWindows = platform() === 'win32';

/**
 * Check if morph-spec command is in PATH
 */
function isMorphSpecInPath() {
  try {
    const command = isWindows ? 'where morph-spec' : 'which morph-spec';
    execSync(command, { stdio: 'ignore' });
    return true;
  } catch {
    return false;
  }
}

/**
 * Detect if nvm-windows is installed
 */
function isUsingNvmWindows() {
  if (!isWindows) return false;

  try {
    const path = process.env.PATH || '';
    return path.includes('nvm4w') || path.includes('\\nvm\\');
  } catch {
    return false;
  }
}

/**
 * Get npm global prefix
 */
function getNpmGlobalPrefix() {
  try {
    const prefix = execSync('npm config get prefix', { encoding: 'utf8' }).trim();
    return prefix;
  } catch {
    return null;
  }
}

// lib files
const REQUIRED_LIB_FILES = [
  'src/lib/analytics/analytics-engine.js',
  'src/lib/tracking/artifact-trail.js',
  'src/lib/context/context-bundler.js',
  'src/lib/context/context-optimizer.js',
  'src/lib/context/context-tracker.js',
  'src/lib/context/core-four-tracker.js',
  'src/lib/execution/fusion-executor.js',
  'src/lib/hops/hop-composer.js',
  'src/lib/context/mcp-optimizer.js',
  'src/lib/agents/micro-agent-factory.js',
  'src/lib/execution/parallel-executor.js',
  'src/lib/hooks/stop-hook-executor.js',
  'src/lib/threads/thread-coordinator.js',
  'src/lib/threads/thread-manager.js',
  'src/lib/trust/trust-manager.js'
];

// command files
const REQUIRED_COMMAND_FILES = [
  'src/commands/agents/agents-fuse.js',
  'src/commands/analytics/analytics.js',
  'src/commands/context/context-prime.js',
  'src/commands/context/core-four.js',
  'src/commands/mcp/mcp.js',
  'src/commands/agents/micro-agent.js',
  'src/commands/agents/squad-template.js',
  'src/commands/threads/thread-template.js',
  'src/commands/threads/threads.js',
  'src/commands/trust/trust.js'
];

// HOP templates (meta-prompts)
const HOP_TEMPLATES = [
  'framework/templates/meta-prompts/squad-leaders/backend-squad.md',
  'framework/templates/meta-prompts/squad-leaders/frontend-squad.md',
  'framework/templates/meta-prompts/parallel-workers/parallel-worker.md',
  'framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md',
  'framework/templates/meta-prompts/hops/hop-wrapper.md',
  'framework/templates/meta-prompts/hops/hop-retry.md',
  'framework/templates/meta-prompts/hops/hop-validation.md',
  'framework/templates/meta-prompts/validators/checkpoint-validator.md',
  'framework/templates/meta-prompts/validators/pre-commit-validator.md',
  'framework/templates/meta-prompts/fusion/fusion-agent.md',
  'framework/templates/meta-prompts/fusion/fusion-aggregator.md',
  'framework/templates/REGISTRY.json'
];

// framework standards
const FRAMEWORK_STANDARDS = [
  'framework/standards/observability/monitoring.md',
  'framework/standards/observability/logging.md',
  'framework/standards/observability/tracing.md',
  'framework/standards/observability/metrics.md',
  'framework/standards/integration/event-driven/service-bus.md',
  'framework/standards/integration/event-driven/cqrs.md',
  'framework/standards/integration/event-driven/event-sourcing.md',
  'framework/standards/data/nosql/cosmos-db.md',
  'framework/standards/data/nosql/cache/redis.md',
  'framework/standards/data/nosql/blob-storage.md',
  'framework/standards/integration/api/rest-design.md',
  'framework/standards/integration/api/graphql.md',
  'framework/standards/integration/api/grpc.md',
  'framework/standards/architecture/ddd/aggregates.md',
  'framework/standards/architecture/ddd/entities.md',
  'framework/standards/architecture/ddd/value-objects.md',
  'framework/standards/data/vector-search/azure-ai-search.md',
  'framework/standards/data/vector-search/rag-chunking.md',
  'framework/standards/context/priming.md',
  'framework/standards/context/bundles.md',
  'framework/standards/context/analytics.md',
  'framework/standards/workflows/thread-management.md',
  'framework/standards/workflows/parallel-execution.md'
];

// required agents expected in agents.json
const REQUIRED_AGENTS = [
  'vector-search-expert',
  'thread-orchestrator',
  'context-optimizer',
  'observability-expert'
];

/**
 * Run full health checks
 */
async function doctorFullCommand(frameworkRoot) {
  console.log(chalk.bold('\n🔬 MORPH-SPEC Full Health Check\n'));
  console.log('─'.repeat(60));

  const checks = [];
  let hasErrors = false;
  let hasWarnings = false;

  const check = (name, passed, warnOnly = false, msg = '') => {
    if (passed) {
      checks.push({ name, status: 'ok' });
    } else {
      checks.push({ name, status: warnOnly ? 'warn' : 'missing', msg });
      if (warnOnly) hasWarnings = true;
      else hasErrors = true;
    }
  };

  // ── 1. Core Libraries ───────────────────────────────────────────────────
  console.log(chalk.cyan(`\n  src/lib/ — Core Libraries (${REQUIRED_LIB_FILES.length} files)`));
  const missingLibs = [];
  for (const f of REQUIRED_LIB_FILES) {
    if (!(await pathExists(join(frameworkRoot, f)))) missingLibs.push(f.split('/').pop());
  }
  check(`Core Libraries (${REQUIRED_LIB_FILES.length - missingLibs.length}/${REQUIRED_LIB_FILES.length})`,
    missingLibs.length === 0, false,
    missingLibs.length > 0 ? `missing: ${missingLibs.join(', ')}` : '');

  // ── 2. Command Files ─────────────────────────────────────────────────────
  console.log(chalk.cyan(`\n  src/commands/ — Command Files (${REQUIRED_COMMAND_FILES.length} files)`));
  const missingCmds = [];
  for (const f of REQUIRED_COMMAND_FILES) {
    if (!(await pathExists(join(frameworkRoot, f)))) missingCmds.push(f.split('/').pop());
  }
  check(`Command Files (${REQUIRED_COMMAND_FILES.length - missingCmds.length}/${REQUIRED_COMMAND_FILES.length})`,
    missingCmds.length === 0, false,
    missingCmds.length > 0 ? `missing: ${missingCmds.join(', ')}` : '');

  // ── 3. HOP Templates ────────────────────────────────────────────────────
  console.log(chalk.cyan(`\n  framework/templates/meta-prompts/ — HOP Templates (${HOP_TEMPLATES.length})`));
  const missingHOPs = [];
  for (const f of HOP_TEMPLATES) {
    if (!(await pathExists(join(frameworkRoot, f)))) missingHOPs.push(f.split('/').pop());
  }
  check(`HOP Templates (${HOP_TEMPLATES.length - missingHOPs.length}/${HOP_TEMPLATES.length})`,
    missingHOPs.length === 0, false,
    missingHOPs.length > 0 ? `missing: ${missingHOPs.join(', ')}` : '');

  // ── 4. Framework Standards ───────────────────────────────────────────────
  console.log(chalk.cyan(`\n  framework/standards/ — Framework Standards (${FRAMEWORK_STANDARDS.length} files)`));
  const missingStds = [];
  for (const f of FRAMEWORK_STANDARDS) {
    if (!(await pathExists(join(frameworkRoot, f)))) missingStds.push(f.replace('framework/standards/', ''));
  }
  check(`Framework Standards (${FRAMEWORK_STANDARDS.length - missingStds.length}/${FRAMEWORK_STANDARDS.length})`,
    missingStds.length === 0, false,
    missingStds.length > 0 ? `missing: ${missingStds.join(', ')}` : '');

  // ── 5. Required Agents in agents.json ───────────────────────────────────
  console.log(chalk.cyan(`\n  .morph/config/agents.json — Required Agents (${REQUIRED_AGENTS.length})`));
  const agentsPath = join(frameworkRoot, '.morph/config/agents.json');
  if (await pathExists(agentsPath)) {
    try {
      const agentsConfig = JSON.parse(await fs.readFile(agentsPath, 'utf8'));
      const agents = agentsConfig.agents || {};
      const missingAgents = REQUIRED_AGENTS.filter(id => !agents[id]);
      check(`Required Agents (${REQUIRED_AGENTS.length - missingAgents.length}/${REQUIRED_AGENTS.length})`,
        missingAgents.length === 0, false,
        missingAgents.length > 0 ? `missing: ${missingAgents.join(', ')}` : '');
    } catch {
      check('agents.json parse', false, false, 'invalid JSON');
    }
  } else {
    check('agents.json', false, false, 'file not found');
  }

  // ── 7. Zero-Touch Workflow Config ───────────────────────────────────────
  console.log(chalk.cyan('\n  framework/workflows/ — Zero-Touch Config'));
  const ztPath = join(frameworkRoot, 'framework/workflows/configs/zero-touch.json');
  check('zero-touch.json workflow config', await pathExists(ztPath));

  // ── 8. state.json schema version ────────────────────────────────────────
  console.log(chalk.cyan('\n  .morph/state.json — Schema Version'));
  const statePath = join(frameworkRoot, '.morph/state.json');
  if (await pathExists(statePath)) {
    try {
      const state = JSON.parse(await fs.readFile(statePath, 'utf8'));
      const isCurrent = state.version === '3.0.0';
      check(`state.json schema (${state.version})`, isCurrent, true,
        isCurrent ? '' : 'run: morph-spec state init --force');
    } catch {
      check('state.json parse', false, false, 'invalid JSON');
    }
  } else {
    check('state.json', false, true, 'not found (run: morph-spec state init)');
  }

  // ── Display ──────────────────────────────────────────────────────────────
  console.log('\n' + '─'.repeat(60));
  console.log(chalk.bold('\nResults:\n'));
  for (const c of checks) {
    if (c.status === 'ok') {
      console.log(chalk.green(`  ✓ ${c.name}`));
    } else if (c.status === 'warn') {
      console.log(chalk.yellow(`  ⚠ ${c.name}`) + chalk.gray(c.msg ? ` (${c.msg})` : ''));
    } else {
      console.log(chalk.red(`  ✗ ${c.name}`) + chalk.gray(c.msg ? ` — ${c.msg}` : ''));
    }
  }

  console.log('');
  const passed = checks.filter(c => c.status === 'ok').length;
  const total = checks.length;

  if (hasErrors) {
    console.log(chalk.red(`\n❌ ${passed}/${total} checks passed — see errors above\n`));
    process.exit(1);
  } else if (hasWarnings) {
    console.log(chalk.yellow(`\n⚠️  ${passed}/${total} checks passed (warnings only)\n`));
  } else {
    console.log(chalk.green(`\n✅ ${passed}/${total} — All checks passed!\n`));
  }
}

export async function doctorCommand(options = {}) {
  // full health check mode
  if (options.full) {
    const frameworkRoot = process.cwd();
    return doctorFullCommand(frameworkRoot);
  }

  const targetPath = process.cwd();

  logger.header('MORPH-SPEC Health Check');

  const checks = [];
  let hasErrors = false;
  let hasWarnings = false;

  // Check versions first
  const cliCheck = await checkCLIOutdated();
  const projectCheck = await checkProjectOutdated(targetPath);

  // CLI Version
  if (cliCheck.latest) {
    if (cliCheck.isOutdated) {
      checks.push({
        name: `CLI version (${cliCheck.current})`,
        status: 'warn',
        msg: `outdated, ${cliCheck.latest} available`
      });
      hasWarnings = true;
    } else {
      checks.push({
        name: `CLI version (${cliCheck.current})`,
        status: 'ok',
        msg: 'latest'
      });
    }
  } else {
    checks.push({
      name: `CLI version (${cliCheck.current})`,
      status: 'ok'
    });
  }

  // Project MORPH version
  if (projectCheck.current) {
    if (projectCheck.isOutdated) {
      checks.push({
        name: `Project MORPH version (${projectCheck.current})`,
        status: 'warn',
        msg: `outdated, ${projectCheck.cliVersion} available`
      });
      hasWarnings = true;
    } else {
      checks.push({
        name: `Project MORPH version (${projectCheck.current})`,
        status: 'ok'
      });
    }
  } else {
    checks.push({
      name: 'Project MORPH version',
      status: 'warn',
      msg: 'not found (legacy installation)'
    });
    hasWarnings = true;
  }

  // Check if morph-spec is in PATH
  const inPath = isMorphSpecInPath();
  if (inPath) {
    checks.push({
      name: 'morph-spec in PATH',
      status: 'ok'
    });
  } else {
    checks.push({
      name: 'morph-spec in PATH',
      status: 'warn',
      msg: 'command not found'
    });
    hasWarnings = true;
  }

  // Check CLAUDE.md
  const claudeMd = join(targetPath, 'CLAUDE.md');
  if (await pathExists(claudeMd)) {
    checks.push({ name: 'CLAUDE.md', status: 'ok' });
  } else {
    checks.push({ name: 'CLAUDE.md', status: 'missing' });
    hasErrors = true;
  }

  // Check .morph folder
  const morphPath = join(targetPath, '.morph');
  if (await pathExists(morphPath)) {
    checks.push({ name: '.morph/', status: 'ok' });
  } else {
    checks.push({ name: '.morph/', status: 'missing' });
    hasErrors = true;
  }

  // Check config.json
  const configPath = join(morphPath, 'config', 'config.json');
  if (await pathExists(configPath)) {
    try {
      const config = await readJson(configPath);
      if (config.project?.name) {
        checks.push({ name: 'config.json', status: 'ok' });
      } else {
        checks.push({ name: 'config.json', status: 'warn', msg: 'project.name not set' });
      }
    } catch {
      checks.push({ name: 'config.json', status: 'error', msg: 'invalid JSON' });
      hasErrors = true;
    }
  } else {
    checks.push({ name: 'config.json', status: 'missing' });
    hasErrors = true;
  }

  // Check agents.json
  const agentsPath = join(morphPath, 'config', 'agents.json');
  if (await pathExists(agentsPath)) {
    checks.push({ name: 'agents.json', status: 'ok' });
  } else {
    checks.push({ name: 'agents.json', status: 'missing' });
    hasErrors = true;
  }

  // Check standards (either in project .morph/standards/ or in framework package)
  const standardsPath = join(morphPath, 'standards');
  const frameworkStdsRoot = join(import.meta.dirname, '..', '..', '..', 'framework', 'standards');
  if (await pathExists(standardsPath)) {
    // Check for core standards in hierarchy (copied by init) or flat layout
    const codingStd = await pathExists(join(standardsPath, 'core', 'coding.md')) ||
                      await pathExists(join(standardsPath, 'coding.md'));
    const archStd   = await pathExists(join(standardsPath, 'core', 'architecture.md')) ||
                      await pathExists(join(standardsPath, 'architecture.md'));
    if (codingStd && archStd) {
      checks.push({ name: 'standards/', status: 'ok' });
    } else {
      checks.push({ name: 'standards/', status: 'warn', msg: 'core standards missing — run morph-spec init' });
      hasWarnings = true;
    }
  } else if (await pathExists(frameworkStdsRoot)) {
    // Framework package has standards (dev/global install mode)
    checks.push({ name: 'standards/', status: 'warn', msg: 'not installed in project (run morph-spec init)' });
    hasWarnings = true;
  } else {
    checks.push({ name: 'standards/', status: 'missing' });
    hasErrors = true;
  }

  // Check templates
  const templatesPath = join(morphPath, 'templates');
  if (await pathExists(templatesPath)) {
    checks.push({ name: 'templates/', status: 'ok' });
  } else {
    checks.push({ name: 'templates/', status: 'missing' });
    hasErrors = true;
  }

  // Check .claude folder
  const claudePath = join(targetPath, '.claude');
  if (await pathExists(claudePath)) {
    const commandsPath = join(claudePath, 'commands');
    const skillsPath = join(claudePath, 'skills');

    const hasCommands = await pathExists(commandsPath);
    const hasSkills = await pathExists(skillsPath);

    if (hasCommands && hasSkills) {
      checks.push({ name: '.claude/', status: 'ok' });

      // Check skills link status
      const skillEntries = await fs.readdir(skillsPath, { withFileTypes: true });
      const skillDirs = skillEntries.filter(e => e.isDirectory() || e.isSymbolicLink());
      let linkedCount = 0;

      for (const dir of skillDirs) {
        try {
          const stat = await fs.lstat(join(skillsPath, dir.name));
          if (stat.isSymbolicLink()) linkedCount++;
        } catch { /* ignore */ }
      }

      if (skillDirs.length > 0) {
        if (linkedCount === skillDirs.length) {
          checks.push({ name: '.claude/skills/ links', status: 'ok', msg: `${linkedCount} categories linked` });
        } else if (linkedCount > 0) {
          checks.push({ name: '.claude/skills/ links', status: 'warn', msg: `${linkedCount}/${skillDirs.length} linked, rest copied` });
          hasWarnings = true;
        } else {
          checks.push({ name: '.claude/skills/ links', status: 'warn', msg: 'all categories copied (no auto-update)' });
          hasWarnings = true;
        }
      }
    } else {
      checks.push({ name: '.claude/', status: 'warn', msg: 'incomplete structure' });
    }
  } else {
    checks.push({ name: '.claude/', status: 'missing' });
    hasErrors = true;
  }

  // Check state.json
  const statePath = join(targetPath, '.morph', 'state.json');
  if (await pathExists(statePath)) {
    try {
      const stateContent = await readJson(statePath);
      if (stateContent.version && stateContent.features) {
        const featureCount = Object.keys(stateContent.features).length;
        checks.push({ name: 'state.json', status: 'ok', msg: `${featureCount} feature(s)` });
      } else {
        checks.push({ name: 'state.json', status: 'warn', msg: 'invalid schema' });
        hasWarnings = true;
      }
    } catch {
      checks.push({ name: 'state.json', status: 'error', msg: 'invalid JSON' });
      hasErrors = true;
    }
  } else {
    checks.push({ name: 'state.json', status: 'warn', msg: 'not initialized (run: morph-spec state init)' });
    hasWarnings = true;
  }

  // Check key templates exist (in framework package, not project copy)
  const frameworkTemplatesRoot = join(import.meta.dirname, '..', '..', '..', 'framework', 'templates');
  const keyTemplates = [
    { name: 'proposal.md', path: join(frameworkTemplatesRoot, 'docs', 'proposal.md') },
    { name: 'spec.md', path: join(frameworkTemplatesRoot, 'docs', 'spec.md') },
    { name: 'tasks.md', path: join(frameworkTemplatesRoot, 'feature', 'tasks.md') },
    { name: 'decisions.md', path: join(frameworkTemplatesRoot, 'feature', 'decisions.md') }
  ];
  const missingTemplates = [];
  for (const template of keyTemplates) {
    if (!(await pathExists(template.path))) {
      missingTemplates.push(template.name);
    }
  }

  if (missingTemplates.length === 0) {
    checks.push({ name: 'key templates', status: 'ok' });
  } else if (missingTemplates.length < keyTemplates.length) {
    checks.push({ name: 'key templates', status: 'warn', msg: `missing: ${missingTemplates.join(', ')}` });
    hasWarnings = true;
  } else {
    checks.push({ name: 'key templates', status: 'missing' });
    hasErrors = true;
  }

  // Check Node.js version
  const nodeVersion = process.version;
  const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0], 10);
  if (majorVersion >= 18) {
    checks.push({ name: `Node.js ${nodeVersion}`, status: 'ok' });
  } else {
    checks.push({ name: `Node.js ${nodeVersion}`, status: 'warn', msg: 'v18+ recommended' });
    hasWarnings = true;
  }

  // Display results
  for (const check of checks) {
    if (check.status === 'ok') {
      console.log(chalk.green(`  ✓ ${check.name}`));
    } else if (check.status === 'warn') {
      console.log(chalk.yellow(`  ⚠ ${check.name}`) + chalk.gray(` (${check.msg})`));
    } else if (check.status === 'missing') {
      console.log(chalk.red(`  ✗ ${check.name}`) + chalk.gray(' (missing)'));
    } else if (check.status === 'error') {
      console.log(chalk.red(`  ✗ ${check.name}`) + chalk.gray(` (${check.msg})`));
    }
  }

  logger.blank();

  if (hasErrors) {
    logger.error('Some checks failed. Run "morph-spec init --force" to fix.');
    process.exit(1);
  } else if (hasWarnings) {
    logger.warn('Some checks need attention.');
    logger.blank();

    if (!inPath) {
      const usingNvm = isUsingNvmWindows();
      const prefix = getNpmGlobalPrefix();

      if (usingNvm && prefix) {
        logger.info('PATH Issue Detected (nvm-windows):');
        logger.dim(`  Add to PATH: ${prefix}`);
        logger.blank();
        logger.dim('  PowerShell (as Administrator):');
        console.log(chalk.gray(`    [Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";${prefix}", "User")`));
        logger.blank();
        logger.dim('  Or use npx instead:');
        logger.dim('    npx @polymorphism-tech/morph-spec init');
        logger.blank();
      } else if (!inPath) {
        logger.info('morph-spec not in PATH:');
        logger.dim('  Use npx instead:');
        logger.dim('    npx @polymorphism-tech/morph-spec init');
        logger.blank();
      }
    }

    if (cliCheck.isOutdated || projectCheck.isOutdated) {
      logger.info('To update:');
      logger.dim('  1. Update the CLI: npm install -g @polymorphism-tech/morph-spec@latest');
      logger.dim('  2. Update the project: morph-spec update');
      logger.blank();
    }
  } else {
    logger.success('All checks passed!');
  }
}
