/**
 * MORPH-SPEC Story Creator
 * Creates self-contained story files with auto-injected Dev Notes
 * Inspired by BMAD Method story-driven development
 */

import fs from 'fs';
import path from 'path';
import yaml from 'yaml';
import ora from 'ora';
import chalk from 'chalk';
import { logger } from '../utils/logger.js';
import { ensureDir, writeFile } from '../utils/file-copier.js';

// ============================================================================
// Helper Functions
// ============================================================================

function readTemplate(templateName) {
  const templatePath = path.join(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/content/.morph/templates', templateName);

  // Fallback to local if in development
  if (!fs.existsSync(templatePath)) {
    const localPath = path.join(process.cwd(), `content/.morph/templates/${templateName}`);
    if (fs.existsSync(localPath)) {
      return fs.readFileSync(localPath, 'utf-8');
    }
    throw new Error(`Template not found: ${templateName}`);
  }

  return fs.readFileSync(templatePath, 'utf-8');
}

function readSpec(featureName) {
  const specPath = path.join(process.cwd(), `.morph/project/outputs/${featureName}/spec.md`);
  const shardedIndexPath = path.join(process.cwd(), `.morph/project/outputs/${featureName}/spec/index.md`);

  // Try sharded spec first (BMAD pattern)
  if (fs.existsSync(shardedIndexPath)) {
    return { isSharded: true, indexPath: shardedIndexPath };
  }

  // Fallback to monolithic spec
  if (fs.existsSync(specPath)) {
    return { isSharded: false, content: fs.readFileSync(specPath, 'utf-8') };
  }

  throw new Error(`Spec not found for feature: ${featureName}`);
}

function extractContextFromSpec(specContent, sectionHeading) {
  // Extract section content between ## heading and next ##
  const regex = new RegExp(`## ${sectionHeading}\\s*([\\s\\S]*?)(?=\\n## |$)`, 'i');
  const match = specContent.match(regex);
  return match ? match[1].trim() : 'See spec.md for complete context';
}

function detectPatterns(tasks, specContent) {
  const patterns = {
    hasEntity: tasks.some(t => /entity|model|domain/i.test(t)),
    hasService: tasks.some(t => /service|business|logic/i.test(t)),
    hasComponent: tasks.some(t => /component|razor|blazor|ui/i.test(t)),
    hasRepository: tasks.some(t => /repository|data|persistence/i.test(t)),
    hasJob: tasks.some(t => /job|background|hangfire|scheduled/i.test(t)),
    hasAI: tasks.some(t => /agent|ai|llm|semantic|rag/i.test(t)),
    hasAzure: tasks.some(t => /bicep|azure|infra|deploy/i.test(t)),
  };

  return patterns;
}

function injectDevNotes(patterns, storyData) {
  const notes = [];

  // Entity pattern
  if (patterns.hasEntity) {
    notes.push('Use Primary Constructor (.NET 10 feature)');
    notes.push('Follow entity pattern: .morph/project/standards/coding.md#entity-pattern');
    notes.push('Navigation properties: configure in DbContext with Fluent API');
  }

  // Service pattern
  if (patterns.hasService) {
    notes.push('Service layer: implement interface-first (IXService → XService)');
    notes.push('Dependency injection: register in Program.cs as Scoped');
    notes.push('Error handling: use Result<T> pattern from standards/architecture.md');
  }

  // Blazor component
  if (patterns.hasComponent) {
    notes.push('UI Library: Fluent UI Blazor (see .morph/project/standards/ui.md)');
    notes.push('Design System: use CSS variables from wwwroot/css/design-system.css');
    notes.push('State management: @inject for services, [Parameter] for props');
  }

  // Repository pattern
  if (patterns.hasRepository) {
    notes.push('Repository pattern: IRepository<T> generic base');
    notes.push('EF Core: use AsNoTracking() for read-only queries');
    notes.push('Transactions: wrap in DbContext.Database.BeginTransactionAsync()');
  }

  // Background job
  if (patterns.hasJob) {
    notes.push('Hangfire: implement IJob interface, register in HangfireConfig.cs');
    notes.push('Retry policy: use [AutomaticRetry(Attempts = 3)]');
    notes.push('Logging: inject ILogger<TJob> for monitoring');
  }

  // AI/Agent
  if (patterns.hasAI) {
    notes.push('Microsoft Agent Framework: use AgentBuilder (not Semantic Kernel)');
    notes.push('Prompts: store in /prompts/{agent-name}.md');
    notes.push('Vector search: use EF Core 10 Vector Search (see standards/vector-search-rag.md)');
  }

  // Azure/IaC
  if (patterns.hasAzure) {
    notes.push('Infrastructure as Code: ALWAYS use Bicep (never portal)');
    notes.push('Cost validation: run morph-spec cost before commit');
    notes.push('Deployment: Azure Container Apps (not App Service)');
  }

  // Add spec reference
  if (storyData.specShard) {
    notes.push(`Spec reference: .morph/project/outputs/${storyData.featureName}/spec/${storyData.specShard}.md`);
  } else {
    notes.push(`Spec reference: .morph/project/outputs/${storyData.featureName}/spec.md`);
  }

  return notes;
}

function renderStory(template, data) {
  let rendered = template;

  // Simple placeholder replacement (non-Mustache for simplicity)
  const replacements = {
    '{{STORY_ID}}': data.storyId || 'STORY-XXX',
    '{{STORY_TITLE}}': data.storyTitle || 'Story Title',
    '{{FEATURE_NAME}}': data.featureName || 'feature-name',
    '{{FEATURE_NAME_TITLE}}': data.featureNameTitle || 'Feature Name',
    '{{EPIC_NAME}}': data.epicName || 'Epic Name',
    '{{DATE}}': new Date().toISOString().split('T')[0],
    '{{STORY_CONTEXT}}': data.context || 'Context not provided',
    '{{SPEC_SHARD}}': data.specShard || 'spec',
    '{{EFFORT}}': data.effort || '1 day',
    '{{CODING_PATTERN}}': data.codingPattern || 'general',
    '{{ARCH_PATTERN}}': data.archPattern || 'clean-architecture',
    '{{STATUS}}': 'Ready',
    '{{CREATED_DATE}}': new Date().toISOString().split('T')[0],
    '{{ADDITIONAL_NOTES}}': data.additionalNotes || '',
  };

  for (const [placeholder, value] of Object.entries(replacements)) {
    rendered = rendered.replaceAll(placeholder, value);
  }

  // Handle arrays (tasks, dev notes, acceptance criteria)
  if (data.tasks && data.tasks.length > 0) {
    const tasksSection = data.tasks.map(t => `- [ ] ${t}`).join('\n');
    rendered = rendered.replace(/{{#TASKS}}[\s\S]*?{{\/TASKS}}/g, tasksSection);
  } else {
    rendered = rendered.replace(/{{#TASKS}}[\s\S]*?{{\/TASKS}}/g, '- [ ] Task 1\n- [ ] Task 2');
  }

  if (data.devNotes && data.devNotes.length > 0) {
    const devNotesSection = data.devNotes.map(n => `- ${n}`).join('\n');
    rendered = rendered.replace(/{{#DEV_NOTES}}[\s\S]*?{{\/DEV_NOTES}}/g, devNotesSection);
  } else {
    rendered = rendered.replace(/{{#DEV_NOTES}}[\s\S]*?{{\/DEV_NOTES}}/g, '- Follow standards in .morph/project/standards/');
  }

  if (data.acceptanceCriteria && data.acceptanceCriteria.length > 0) {
    const acSection = data.acceptanceCriteria.map(ac =>
      `- [ ] **GIVEN** ${ac.given} **WHEN** ${ac.when} **THEN** ${ac.then}`
    ).join('\n');
    rendered = rendered.replace(/{{#ACCEPTANCE_CRITERIA}}[\s\S]*?{{\/ACCEPTANCE_CRITERIA}}/g, acSection);
  } else {
    rendered = rendered.replace(/{{#ACCEPTANCE_CRITERIA}}[\s\S]*?{{\/ACCEPTANCE_CRITERIA}}/g,
      '- [ ] **GIVEN** [condition] **WHEN** [action] **THEN** [result]');
  }

  // Clean up remaining Mustache sections (empty arrays)
  rendered = rendered.replace(/{{#\w+}}[\s\S]*?{{\/\w+}}/g, '');

  return rendered;
}

function toTitleCase(str) {
  return str
    .split('-')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ');
}

// ============================================================================
// Command Function
// ============================================================================

export async function createStoryCommand(feature, storyId, options) {
  logger.header('MORPH-SPEC Story Creator');
  logger.dim(`Feature: ${feature}`);
  logger.dim(`Story ID: ${storyId}`);
  logger.blank();

  const spinner = ora('Creating story...').start();

  try {
    // Read template
    const template = readTemplate('story.md');

    // Read spec (detect if sharded)
    const spec = readSpec(feature);

    // Build story data
    const storyData = {
      featureName: feature,
      featureNameTitle: toTitleCase(feature),
      storyId,
      storyTitle: options.title || 'Story Title',
      epicName: options.epic || toTitleCase(feature),
      context: options.context || (spec.isSharded
        ? 'See sharded spec for complete context'
        : extractContextFromSpec(spec.content, 'Overview')),
      specShard: spec.isSharded ? 'index' : null,
      tasks: options.tasks ? options.tasks.split(',').map(t => t.trim()) : ['Task 1', 'Task 2'],
      effort: '1 day',
      additionalNotes: '',
    };

    // Detect patterns and inject Dev Notes
    const patterns = detectPatterns(storyData.tasks, spec.content || '');
    storyData.devNotes = injectDevNotes(patterns, storyData);

    // Default acceptance criteria
    storyData.acceptanceCriteria = [
      { given: '[condition]', when: '[action]', then: '[expected result]' },
    ];

    // Render story
    const renderedStory = renderStory(template, storyData);

    // Output
    const outputDir = path.join(process.cwd(), `.morph/project/outputs/${feature}/stories`);
    const outputPath = path.join(outputDir, `${storyId}.md`);

    if (options.dryRun) {
      spinner.info('Dry run - preview only');
      logger.blank();
      console.log(renderedStory);
      logger.blank();
      logger.dim(`Would be written to: ${outputPath}`);
    } else {
      // Ensure directory exists
      await ensureDir(outputDir);

      // Write file
      await writeFile(outputPath, renderedStory);

      // ============================================================================
      // Update sprint-status.yaml (create if doesn't exist)
      // ============================================================================
      const sprintStatusPath = path.join(process.cwd(), `.morph/project/outputs/${feature}/sprint-status.yaml`);
      let sprintStatus;

      if (fs.existsSync(sprintStatusPath)) {
        // Load existing
        const content = fs.readFileSync(sprintStatusPath, 'utf-8');
        sprintStatus = yaml.parse(content);
      } else {
        // Create new
        sprintStatus = {
          feature: feature,
          epic: options.epic || toTitleCase(feature),
          created: new Date().toISOString().split('T')[0],
          updated: new Date().toISOString().split('T')[0],
          stories: [],
          metrics: {
            total_stories: 0,
            ready: 0,
            in_progress: 0,
            ready_for_qa: 0,
            done: 0,
            completion_percent: 0
          },
          current: null,
          next: null
        };
      }

      // Add new story to sprint-status
      sprintStatus.stories.push({
        id: storyId,
        title: storyData.storyTitle,
        file: `stories/${storyId}.md`,
        status: 'ready',
        created: new Date().toISOString().split('T')[0],
        assigned: null,
        started: null,
        completed: null
      });

      // Update metrics
      sprintStatus.metrics.total_stories = sprintStatus.stories.length;
      sprintStatus.metrics.ready = sprintStatus.stories.filter(s => s.status === 'ready').length;
      sprintStatus.metrics.in_progress = sprintStatus.stories.filter(s => s.status === 'in_progress').length;
      sprintStatus.metrics.ready_for_qa = sprintStatus.stories.filter(s => s.status === 'ready_for_qa').length;
      sprintStatus.metrics.done = sprintStatus.stories.filter(s => s.status === 'done').length;
      sprintStatus.metrics.completion_percent = sprintStatus.stories.length > 0
        ? Math.round((sprintStatus.metrics.done / sprintStatus.stories.length) * 100)
        : 0;
      sprintStatus.updated = new Date().toISOString().split('T')[0];

      // Set next story if null
      if (!sprintStatus.next) {
        sprintStatus.next = {
          story_id: storyId,
          recommendation: `Story ${storyId} is ready for development`
        };
      }

      // Write sprint-status.yaml
      const yamlContent = yaml.stringify(sprintStatus);
      fs.writeFileSync(sprintStatusPath, yamlContent);

      spinner.succeed('Story created!');
      logger.blank();

      logger.success(`Story file: ${chalk.cyan(outputPath)}`);
      logger.success(`Updated: ${chalk.cyan('sprint-status.yaml')}`);
      logger.blank();

      logger.header('Dev Notes Auto-Injected:');
      storyData.devNotes.forEach(note => logger.dim(`  - ${note}`));
      logger.blank();

      logger.header('Next Steps:');
      logger.dim('  1. Review and customize story file');
      logger.dim('  2. Run in fresh Claude session: /dev → implement story');
      logger.dim('  3. Dev adds implementation notes');
      logger.dim('  4. QA reviews and adds QA notes');
      logger.blank();
    }

  } catch (error) {
    spinner.fail('Failed to create story');
    logger.error(error.message);
    process.exit(1);
  }
}
