import { ConfigManager } from '../../config/config-manager.js';
import { StorageManager } from '../../storage/storage-manager.js';
import { MCPError } from '../../utils/error-handler.js';
import { 
  Sprint, 
  Story, 
  Epic, 
  SprintPlanningSession,
  SprintRetrospective,
  StandupReport,
  VelocityReport,
  BurndownChart,
  SprintStatus,
  BacklogFilter,
  CreateSprintOptions,
  CreateStoryOptions,
  CreateEpicOptions,
  SprintPlanningOptions,
  StandupOptions,
  RetrospectiveOptions,
  StoryUpdateOptions,
  VelocityOptions,
  EpicUpdateOptions,
  SprintUpdateOptions
} from './types.js';
import { promises as fs } from 'fs';
import path from 'path';

export class AgileManager {
  private configManager: ConfigManager;
  private storageManager: StorageManager;
  private agileDataPath: string;
  private sprints: Map<string, Sprint>;
  private stories: Map<string, Story>;
  private epics: Map<string, Epic>;
  private standups: StandupReport[];
  private retrospectives: SprintRetrospective[];

  constructor(configManager: ConfigManager) {
    this.configManager = configManager;
    this.storageManager = new StorageManager();
    this.agileDataPath = '';
    this.sprints = new Map();
    this.stories = new Map();
    this.epics = new Map();
    this.standups = [];
    this.retrospectives = [];
  }

  async initialize(): Promise<void> {
    const location = await this.storageManager.getStorageLocation();
    this.agileDataPath = path.join(location.data, 'agile');
    await this.loadExistingData();
    
    // Create sample data only in development mode and if explicitly enabled
    const isDev = process.env.NODE_ENV === 'development' || process.env.ATLAS_DEV === 'true';
    const shouldCreateSampleData = process.env.ATLAS_CREATE_SAMPLE_DATA === 'true';
    
    if ((isDev || shouldCreateSampleData) && this.sprints.size === 0 && this.stories.size === 0 && this.epics.size === 0) {
      console.log('🔧 Development mode detected - creating sample agile data for dashboard testing');
      await this.createSampleData();
    }
  }

  private async loadExistingData(): Promise<void> {
    try {
      // Load sprints
      const sprintsPath = path.join(this.agileDataPath, 'sprints.json');
      if (await this.fileExists(sprintsPath)) {
        const sprintsData = JSON.parse(await fs.readFile(sprintsPath, 'utf-8'));
        sprintsData.forEach((sprint: Sprint) => {
          sprint.startDate = new Date(sprint.startDate);
          sprint.endDate = new Date(sprint.endDate);
          this.sprints.set(sprint.id, sprint);
        });
      }

      // Load stories
      const storiesPath = path.join(this.agileDataPath, 'stories.json');
      if (await this.fileExists(storiesPath)) {
        const storiesData = JSON.parse(await fs.readFile(storiesPath, 'utf-8'));
        storiesData.forEach((story: Story) => {
          story.createdAt = new Date(story.createdAt);
          story.updatedAt = new Date(story.updatedAt);
          
          // Initialize required arrays if undefined
          story.acceptanceCriteria = story.acceptanceCriteria || [];
          story.tags = story.tags || [];
          story.dependencies = story.dependencies || [];
          story.watchers = story.watchers || [];
          
          // Initialize required objects
          story.timeTracking = story.timeTracking || {
            estimated: 0,
            actual: 0,
            remaining: 0,
            logs: []
          };
          story.attachments = story.attachments || [];
          story.comments = story.comments || [];
          story.customFields = story.customFields || [];
          story.subtasks = story.subtasks || [];
          
          // Convert optional dates
          if (story.dueDate) story.dueDate = new Date(story.dueDate);
          if (story.startDate) story.startDate = new Date(story.startDate);
          if (story.completedAt) story.completedAt = new Date(story.completedAt);
          
          this.stories.set(story.id, story);
        });
      }

      // Load epics
      const epicsPath = path.join(this.agileDataPath, 'epics.json');
      if (await this.fileExists(epicsPath)) {
        const epicsData = JSON.parse(await fs.readFile(epicsPath, 'utf-8'));
        epicsData.forEach((epic: Epic) => {
          epic.createdAt = new Date(epic.createdAt);
          epic.updatedAt = new Date(epic.updatedAt);
          this.epics.set(epic.id, epic);
        });
      }

      // Load standups
      const standupsPath = path.join(this.agileDataPath, 'standups.json');
      if (await this.fileExists(standupsPath)) {
        const standupsData = JSON.parse(await fs.readFile(standupsPath, 'utf-8'));
        this.standups = standupsData.map((standup: any) => ({
          ...standup,
          date: new Date(standup.date)
        }));
      }

      // Load retrospectives
      const retrospectivesPath = path.join(this.agileDataPath, 'retrospectives.json');
      if (await this.fileExists(retrospectivesPath)) {
        const retrospectivesData = JSON.parse(await fs.readFile(retrospectivesPath, 'utf-8'));
        this.retrospectives = retrospectivesData.map((retro: any) => ({
          ...retro,
          date: new Date(retro.date)
        }));
      }
    } catch (error) {
      console.error('Error loading agile data:', error);
    }
    
    // Validate and repair data integrity after loading
    const validationResult = await this.validateAndRepairDataIntegrity();
    if (!validationResult.valid && validationResult.repairs.length > 0) {
      console.log('🔧 Agile data integrity repairs performed:');
      validationResult.repairs.forEach(repair => console.log(`  - ${repair}`));
    }
  }

  async validateAndRepairDataIntegrity(): Promise<{valid: boolean; repairs: string[]}> {
    const repairs: string[] = [];
    let dataModified = false;

    // Check data version to determine repair strategy
    const dataVersion = await this.getDataVersion();
    const needsEpicSync = this.compareVersions(dataVersion, '1.0.19') < 0;
    
    if (needsEpicSync) {
      repairs.push(`Detected data version ${dataVersion} < 1.0.19 - performing epic-story relationship migration`);
    }

    // Step 1: Build a map of valid epic IDs
    const validEpicIds = new Set(this.epics.keys());
    
    // Step 2: Validate and repair story-epic relationships
    if (!needsEpicSync) {
      // For v1.0.19+ data: Only remove truly invalid epic references
      for (const [storyId, story] of this.stories.entries()) {
        if (story.epic && !validEpicIds.has(story.epic)) {
          repairs.push(`Story ${storyId} had invalid epic reference ${story.epic} - removed`);
          story.epic = undefined;
          story.updatedAt = new Date();
          dataModified = true;
        }
      }
    }
    // For pre-v1.0.19 data: Keep all epic references that point to valid epics

    // Step 3: Rebuild epic.storyIds arrays from story references
    if (needsEpicSync) {
      // For pre-v1.0.19 data: Always rebuild epic.storyIds from story references
      repairs.push('Rebuilding all epic.storyIds arrays from story references');
      
      // Clear all epic.storyIds arrays first
      for (const [epicId, epic] of this.epics.entries()) {
        epic.storyIds = [];
        epic.updatedAt = new Date();
      }
      
      // Rebuild from story references
      for (const [storyId, story] of this.stories.entries()) {
        if (story.epic && this.epics.has(story.epic)) {
          const epic = this.epics.get(story.epic)!;
          epic.storyIds.push(storyId);
          repairs.push(`  - Added story ${storyId} to epic ${story.epic}`);
        }
      }
      
      dataModified = true;
    } else {
      // For v1.0.19+ data: Only fix discrepancies
      const epicStoryMap = new Map<string, string[]>();
      
      // Initialize empty arrays for all epics
      for (const epicId of validEpicIds) {
        epicStoryMap.set(epicId, []);
      }
      
      // Collect all valid story-epic relationships
      for (const [storyId, story] of this.stories.entries()) {
        if (story.epic && validEpicIds.has(story.epic)) {
          epicStoryMap.get(story.epic)!.push(storyId);
        }
      }
      
      // Update epic.storyIds arrays only if they differ
      for (const [epicId, epic] of this.epics.entries()) {
        const correctStoryIds = epicStoryMap.get(epicId) || [];
        const currentStoryIds = epic.storyIds || [];
        
        // Check if arrays differ
        if (JSON.stringify(correctStoryIds.sort()) !== JSON.stringify(currentStoryIds.sort())) {
          repairs.push(`Epic ${epicId} storyIds updated: ${currentStoryIds.length} -> ${correctStoryIds.length} stories`);
          epic.storyIds = correctStoryIds;
          epic.updatedAt = new Date();
          dataModified = true;
        }
      }
    }

    // Step 4: Validate sprint-story relationships
    const validSprintIds = new Set(this.sprints.keys());
    for (const [storyId, story] of this.stories.entries()) {
      if (story.sprintId && !validSprintIds.has(story.sprintId)) {
        repairs.push(`Story ${storyId} had invalid sprint reference ${story.sprintId} - removed`);
        story.sprintId = undefined;
        story.updatedAt = new Date();
        dataModified = true;
      }
    }

    // Save changes if any repairs were made
    if (dataModified) {
      await Promise.all([
        this.saveStories(),
        this.saveEpics(),
        this.saveSprints()
      ]);
      
      // Update version if we performed a migration
      if (needsEpicSync) {
        await this.updateDataVersion('1.0.19');
        repairs.push('Updated data version to 1.0.19');
      }
    }

    return {
      valid: repairs.length === 0,
      repairs
    };
  }

  private async fileExists(filePath: string): Promise<boolean> {
    try {
      await fs.access(filePath);
      return true;
    } catch {
      return false;
    }
  }

  async createSprint(options: CreateSprintOptions): Promise<Sprint> {
    const startDate = options.startDate || new Date();
    const sprint: Sprint = {
      id: `sprint-${this.generateId()}`,
      name: options.name,
      goal: options.goal,
      status: 'active',
      startDate: startDate,
      endDate: new Date(startDate.getTime() + (options.duration * 24 * 60 * 60 * 1000)),
      duration: options.duration,
      team: options.team || [],
      storyIds: [],
      epicIds: [],
      capacity: 0,
      velocity: 0,
      burndownData: [],
    };

    this.sprints.set(sprint.id, sprint);
    
    // Add initial stories if provided
    if (options.initialStoryIds && options.initialStoryIds.length > 0) {
      for (const storyId of options.initialStoryIds) {
        try {
          await this.addStoryToSprint(storyId, sprint.id);
        } catch (error) {
          console.error(`Failed to add story ${storyId} to sprint: ${error}`);
        }
      }
    }
    
    await this.saveSprints();
    
    return sprint;
  }

  async addStory(options: CreateStoryOptions): Promise<Story> {
    // Handle epicId alias
    const epicId = (options as any).epicId || options.epic;
    
    // Convert legacy string acceptanceCriteria to structured format
    let acceptanceCriteria: any[] = [];
    if (options.acceptanceCriteria) {
      if (typeof options.acceptanceCriteria[0] === 'string') {
        // Legacy format - convert strings to structured criteria
        acceptanceCriteria = (options.acceptanceCriteria as string[]).map((desc, idx) => ({
          id: `ac-${Date.now()}-${idx}`,
          description: desc,
          status: 'pending' as const,
        }));
      } else {
        // New structured format
        acceptanceCriteria = options.acceptanceCriteria as any[];
      }
    }
    
    const story: Story = {
      id: `story-${this.generateId()}`,
      title: options.title,
      description: options.description,
      status: options.sprintId ? 'todo' : 'backlog' as any,
      priority: options.priority || 'medium',
      storyPoints: options.storyPoints || 0,
      acceptanceCriteria: acceptanceCriteria,
      acceptanceCriteriaLegacy: typeof options.acceptanceCriteria?.[0] === 'string' 
        ? options.acceptanceCriteria as string[] 
        : undefined,
      assignee: options.assignee,
      epic: epicId,
      sprintId: options.sprintId,
      createdAt: new Date(),
      updatedAt: new Date(),
      hoursSpent: 0,
      tags: options.tags || [],
      // Initialize new enhanced properties
      dependencies: options.dependencies || [],
      timeTracking: options.timeEstimate ? { 
        estimated: options.timeEstimate,
        actual: 0,
        remaining: options.timeEstimate,
        logs: []
      } : {
        estimated: 0,
        actual: 0,
        remaining: 0,
        logs: []
      },
      attachments: [],
      comments: [],
      customFields: [],
      subtasks: [],
      watchers: options.watchers || [],
      groomedWithUserFeedback: options.groomedWithUserFeedback ?? false,
    };

    this.stories.set(story.id, story);

    // Add to sprint if specified
    if (options.sprintId && this.sprints.has(options.sprintId)) {
      const sprint = this.sprints.get(options.sprintId)!;
      sprint.storyIds.push(story.id);
      this.sprints.set(sprint.id, sprint);
      await this.saveSprints();
    }

    // Add to epic if specified
    if (epicId && this.epics.has(epicId)) {
      const epic = this.epics.get(epicId)!;
      if (!epic.storyIds.includes(story.id)) {
        epic.storyIds.push(story.id);
        epic.updatedAt = new Date();
        this.epics.set(epic.id, epic);
        await this.saveEpics();
      }
    }

    await this.saveStories();
    
    // Return with epicId for API compatibility
    return { ...story, epicId: story.epic } as any;
  }

  async createEpic(options: CreateEpicOptions): Promise<Epic> {
    const epic: Epic = {
      id: `epic-${this.generateId()}`,
      title: options.title,
      description: options.description,
      status: 'planned',
      priority: options.priority || 'medium',
      goals: options.goals || [],
      owner: options.owner,
      createdAt: new Date(),
      updatedAt: new Date(),
      storyIds: [],
      stories: [],
      progress: 0,
    };

    this.epics.set(epic.id, epic);

    // Handle stories if provided
    const storiesData = (options as any).stories;
    if (storiesData && Array.isArray(storiesData)) {
      const createdStories = [];
      let totalPoints = 0;
      
      for (const storyData of storiesData) {
        const story = await this.addStory({
          title: storyData.title,
          description: storyData.description,
          storyPoints: storyData.storyPoints || 0,
          priority: storyData.priority || 'medium',
          acceptanceCriteria: storyData.acceptanceCriteria || [],
          epic: epic.id,
        });
        
        epic.storyIds.push(story.id);
        createdStories.push(story);
        totalPoints += story.storyPoints;
      }
      
      // Return epic with stories array populated
      const epicWithStories = { ...epic, stories: createdStories, totalStoryPoints: totalPoints };
      await this.saveEpics();
      return epicWithStories as any;
    }
    
    await this.saveEpics();
    return epic;
  }

  async saveEpic(epic: Epic): Promise<void> {
    this.epics.set(epic.id, epic);
    await this.saveEpics();
  }

  async updateEpic(epicId: string, updates: EpicUpdateOptions): Promise<Epic> {
    if (!this.epics.has(epicId)) {
      throw new Error(`Epic ${epicId} not found`);
    }

    const epic = this.epics.get(epicId)!;
    
    // Update basic properties
    if (updates.title !== undefined) epic.title = updates.title;
    if (updates.description !== undefined) epic.description = updates.description;
    if (updates.status !== undefined) epic.status = updates.status;
    if (updates.priority !== undefined) epic.priority = updates.priority;
    if (updates.goals !== undefined) epic.goals = updates.goals;
    if (updates.owner !== undefined) epic.owner = updates.owner;
    
    // Update metadata if provided
    if (updates.metadata !== undefined) {
      epic.metadata = {
        ...epic.metadata,
        ...updates.metadata
      };
    }
    
    // Update timestamp
    epic.updatedAt = new Date();
    
    // Recalculate progress based on story statuses
    if (epic.storyIds.length > 0) {
      const stories = epic.storyIds
        .map(id => this.stories.get(id))
        .filter(story => story !== undefined) as Story[];
      
      const totalStories = stories.length;
      const completedStories = stories.filter(s => s.status === 'done').length;
      epic.progress = totalStories > 0 ? Math.round((completedStories / totalStories) * 100) : 0;
    }
    
    this.epics.set(epic.id, epic);
    await this.saveEpics();
    
    return epic;
  }

  async updateSprint(sprintId: string, updates: SprintUpdateOptions): Promise<Sprint> {
    if (!this.sprints.has(sprintId)) {
      throw new Error(`Sprint ${sprintId} not found`);
    }

    const sprint = this.sprints.get(sprintId)!;
    
    // Update basic properties
    if (updates.name !== undefined) sprint.name = updates.name;
    if (updates.goal !== undefined) sprint.goal = updates.goal;
    if (updates.status !== undefined) sprint.status = updates.status;
    if (updates.endDate !== undefined) sprint.endDate = updates.endDate;
    if (updates.team !== undefined) sprint.team = updates.team;
    if (updates.capacity !== undefined) sprint.capacity = updates.capacity;
    
    // Recalculate duration if end date changed
    if (updates.endDate !== undefined) {
      sprint.duration = Math.ceil(
        (sprint.endDate.getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24)
      );
    }
    
    // Recalculate velocity if sprint is completed
    if (sprint.status === 'completed') {
      const sprintStories = sprint.storyIds
        .map(id => this.stories.get(id))
        .filter(story => story !== undefined) as Story[];
      
      sprint.velocity = sprintStories
        .filter(story => story.status === 'done')
        .reduce((sum, story) => sum + story.storyPoints, 0);
    }
    
    this.sprints.set(sprint.id, sprint);
    await this.saveSprints();
    
    return sprint;
  }

  async addStoryToSprint(storyId: string, sprintId: string): Promise<void> {
    const story = this.stories.get(storyId);
    const sprint = this.sprints.get(sprintId);
    
    if (!story) {
      throw new Error(`Story ${storyId} not found`);
    }
    if (!sprint) {
      throw new Error(`Sprint ${sprintId} not found`);
    }

    // Update story
    story.sprintId = sprintId;
    story.updatedAt = new Date();
    this.stories.set(story.id, story);

    // Update sprint
    if (!sprint.storyIds.includes(storyId)) {
      sprint.storyIds.push(storyId);
      this.sprints.set(sprint.id, sprint);
    }

    // Save both
    await Promise.all([this.saveStories(), this.saveSprints()]);
  }

  async conductSprintPlanning(options: SprintPlanningOptions): Promise<SprintPlanningSession> {
    if (!this.sprints.has(options.sprintId)) {
      throw new Error(`Sprint ${options.sprintId} not found`);
    }

    const sprint = this.sprints.get(options.sprintId)!;
    
    const planning: SprintPlanningSession = {
      id: `planning-${Date.now()}`,
      sprintId: options.sprintId,
      date: new Date(),
      attendees: options.attendees,
      capacity: options.capacity || 0,
      commitments: options.commitments,
      notes: '',
      outcome: 'ready',
    };

    // Update sprint with planning results
    sprint.capacity = planning.capacity;
    sprint.storyIds = [...new Set([...sprint.storyIds, ...planning.commitments])];
    sprint.status = 'active';
    
    this.sprints.set(sprint.id, sprint);
    await this.saveSprints();

    return planning;
  }

  async recordStandup(options: StandupOptions): Promise<StandupReport> {
    const standup: StandupReport = {
      id: `standup-${Date.now()}`,
      sprintId: options.sprintId,
      attendee: options.attendee,
      date: new Date(),
      yesterday: options.yesterday,
      today: options.today,
      blockers: options.blockers,
    };

    this.standups.push(standup);
    await this.saveStandups();

    return standup;
  }

  async conductRetrospective(options: RetrospectiveOptions): Promise<SprintRetrospective> {
    if (!this.sprints.has(options.sprintId)) {
      throw new Error(`Sprint ${options.sprintId} not found`);
    }

    const retrospective: SprintRetrospective = {
      id: `retro-${Date.now()}`,
      sprintId: options.sprintId,
      date: new Date(),
      attendees: options.attendees,
      whatWentWell: options.whatWentWell,
      whatCanImprove: options.whatCanImprove,
      actionItems: options.actionItems,
      sprintRating: 7, // Default rating
    };

    this.retrospectives.push(retrospective);
    await this.saveRetrospectives();

    // Mark sprint as completed
    const sprint = this.sprints.get(options.sprintId)!;
    sprint.status = 'completed';
    this.sprints.set(sprint.id, sprint);
    await this.saveSprints();

    return retrospective;
  }

  async updateStoryStatus(storyId: string, updates: StoryUpdateOptions): Promise<Story> {
    if (!this.stories.has(storyId)) {
      throw new MCPError({
        message: `Story ${storyId} not found`,
        code: 'STORY_NOT_FOUND',
        category: 'validation',
        details: { storyId },
        recoverable: false,
        suggestedActions: [
          'Check that the story ID is correct',
          'Use list_agile_backlog to see available stories',
          'Ensure the story was not deleted'
        ]
      });
    }

    const story = this.stories.get(storyId)!;
    
    // Validate status transition to in_progress
    if (updates.status === 'in_progress' && !story.groomedWithUserFeedback) {
      throw new MCPError({
        message: 'Story must be groomed with user feedback before moving to in_progress',
        code: 'STORY_NOT_GROOMED',
        category: 'validation',
        details: {
          storyId,
          currentStatus: story.status,
          targetStatus: updates.status,
          groomedWithUserFeedback: story.groomedWithUserFeedback
        },
        recoverable: true,
        suggestedActions: [
          'Mark the story as groomed with user feedback first',
          'Run a grooming session with stakeholders',
          'Update the story with groomedWithUserFeedback: true'
        ]
      });
    }
    
    // Validate documentation requirements when moving from todo/backlog
    if (story.status === 'todo' || story.status === 'backlog') {
      if (updates.status && updates.status !== 'todo' && updates.status !== 'backlog') {
        // Story is moving out of todo/backlog, check documentation
        if (story.documentationStatus !== 'approved') {
          throw new MCPError({
            message: 'Story documentation must be approved before moving out of To Do',
            code: 'DOCUMENTATION_NOT_APPROVED',
            category: 'validation',
            details: {
              storyId,
              currentStatus: story.status,
              targetStatus: updates.status,
              documentationStatus: story.documentationStatus || 'pending',
              designDocumentUrl: story.designDocumentUrl,
              implementationDocumentUrl: story.implementationDocumentUrl
            },
            recoverable: true,
            suggestedActions: [
              'Add design document URL to the story',
              'Add implementation document URL to the story',
              'Get documentation reviewed and approved',
              'Update documentationStatus to "approved"'
            ]
          });
        }
      }
    }
    
    // Update basic properties
    if (updates.status !== undefined) story.status = updates.status;
    if (updates.title !== undefined) story.title = updates.title;
    if (updates.description !== undefined) story.description = updates.description;
    if (updates.priority !== undefined) story.priority = updates.priority;
    if (updates.storyPoints !== undefined) story.storyPoints = updates.storyPoints;
    if (updates.assignee !== undefined) story.assignee = updates.assignee;
    if (updates.tags !== undefined) story.tags = updates.tags;
    if (updates.groomedWithUserFeedback !== undefined) story.groomedWithUserFeedback = updates.groomedWithUserFeedback;
    if (updates.designDocumentUrl !== undefined) story.designDocumentUrl = updates.designDocumentUrl;
    if (updates.implementationDocumentUrl !== undefined) story.implementationDocumentUrl = updates.implementationDocumentUrl;
    if (updates.documentationStatus !== undefined) story.documentationStatus = updates.documentationStatus;
    if (updates.documentationLastReviewed !== undefined) story.documentationLastReviewed = updates.documentationLastReviewed;
    if (updates.documentationReviewers !== undefined) story.documentationReviewers = updates.documentationReviewers;
    
    // Handle epic changes
    if (updates.epic !== undefined) {
      const oldEpicId = story.epic;
      const newEpicId = updates.epic;
      
      // Remove from old epic if it exists
      if (oldEpicId && this.epics.has(oldEpicId)) {
        const oldEpic = this.epics.get(oldEpicId)!;
        oldEpic.storyIds = oldEpic.storyIds.filter(id => id !== storyId);
        oldEpic.updatedAt = new Date();
        this.epics.set(oldEpicId, oldEpic);
      }
      
      // Add to new epic if it exists
      if (newEpicId && this.epics.has(newEpicId)) {
        const newEpic = this.epics.get(newEpicId)!;
        if (!newEpic.storyIds.includes(storyId)) {
          newEpic.storyIds.push(storyId);
          newEpic.updatedAt = new Date();
          this.epics.set(newEpicId, newEpic);
        }
        story.epic = newEpicId;
      } else if (newEpicId) {
        throw new MCPError({
          message: `Epic ${newEpicId} not found`,
          code: 'EPIC_NOT_FOUND',
          category: 'validation',
          details: { epicId: newEpicId, storyId },
          recoverable: false,
          suggestedActions: [
            'Check that the epic ID is correct',
            'Use list_epics to see available epics',
            'Create the epic first before assigning stories'
          ]
        });
      } else {
        story.epic = undefined;
      }
      
      await this.saveEpics();
    }
    
    // Handle acceptance criteria updates
    if (updates.acceptanceCriteria !== undefined) {
      if (typeof updates.acceptanceCriteria[0] === 'string') {
        // Legacy format - convert strings to structured criteria
        story.acceptanceCriteria = (updates.acceptanceCriteria as string[]).map((desc, idx) => ({
          id: `ac-${Date.now()}-${idx}`,
          description: desc,
          status: 'pending' as const,
        }));
        story.acceptanceCriteriaLegacy = updates.acceptanceCriteria as string[];
      } else {
        // New structured format
        story.acceptanceCriteria = updates.acceptanceCriteria as any[];
      }
    }
    
    // Update enhanced properties
    if (updates.dependencies !== undefined) story.dependencies = updates.dependencies;
    if (updates.timeTracking !== undefined) {
      story.timeTracking = { ...story.timeTracking, ...updates.timeTracking };
    }
    
    // Handle time tracking
    if (updates.hoursSpent !== undefined) {
      story.hoursSpent = updates.hoursSpent;
      if (!story.timeTracking) {
        story.timeTracking = {
          estimated: 0,
          actual: updates.hoursSpent,
          remaining: 0,
          logs: []
        };
      } else {
        story.timeTracking.actual = updates.hoursSpent;
      }
    }
    
    story.updatedAt = new Date();

    this.stories.set(storyId, story);
    await this.saveStories();

    return story;
  }

  async generateBurndownChart(sprintId: string): Promise<BurndownChart> {
    if (!this.sprints.has(sprintId)) {
      throw new Error(`Sprint ${sprintId} not found`);
    }

    const sprint = this.sprints.get(sprintId)!;
    const sprintStories = sprint.storyIds
      .map(id => this.stories.get(id))
      .filter(story => story !== undefined) as Story[];

    const totalStoryPoints = sprintStories.reduce((sum, story) => sum + story.storyPoints, 0);
    const completedPoints = sprintStories
      .filter(story => story.status === 'done')
      .reduce((sum, story) => sum + story.storyPoints, 0);

    const sprintStart = sprint.startDate;
    const sprintEnd = sprint.endDate;
    const totalDays = Math.ceil((sprintEnd.getTime() - sprintStart.getTime()) / (1000 * 60 * 60 * 24));
    const currentDate = new Date();
    const daysElapsed = Math.min(
      Math.ceil((currentDate.getTime() - sprintStart.getTime()) / (1000 * 60 * 60 * 24)),
      totalDays
    );

    const idealBurnRate = totalStoryPoints / totalDays;
    const dailyData = [];

    for (let day = 0; day <= Math.min(daysElapsed, totalDays); day++) {
      const idealRemaining = Math.max(0, totalStoryPoints - (idealBurnRate * day));
      const actualRemaining = day === daysElapsed ? totalStoryPoints - completedPoints : idealRemaining;
      
      dailyData.push({
        day,
        idealPoints: idealRemaining,
        actualPoints: actualRemaining,
        remainingPoints: actualRemaining,
        completedPoints: totalStoryPoints - actualRemaining,
      });
    }

    const onTrack = completedPoints >= (totalStoryPoints * (daysElapsed / totalDays)) * 0.8;

    return {
      sprintId,
      totalStoryPoints,
      completedPoints,
      sprintDays: totalDays,
      dailyData,
      onTrack,
      daysRemaining: Math.max(0, totalDays - daysElapsed),
      trend: completedPoints > 0 ? 'improving' : 'stable',
    };
  }

  async generateVelocityReport(options: VelocityOptions): Promise<VelocityReport> {
    const completedSprints = Array.from(this.sprints.values())
      .filter(sprint => sprint.status === 'completed')
      .sort((a, b) => b.endDate.getTime() - a.endDate.getTime())
      .slice(0, options.lastNSprints);

    if (completedSprints.length === 0) {
      throw new Error('No completed sprints found for velocity calculation');
    }

    const sprintData = completedSprints.map(sprint => {
      const sprintStories = sprint.storyIds
        .map(id => this.stories.get(id))
        .filter(story => story !== undefined) as Story[];
      
      const velocity = sprintStories
        .filter(story => story.status === 'done')
        .reduce((sum, story) => sum + story.storyPoints, 0);
      
      const totalPoints = sprintStories.reduce((sum, story) => sum + story.storyPoints, 0);
      const completionRate = totalPoints > 0 ? Math.round((velocity / totalPoints) * 100) : 0;

      return {
        sprintName: sprint.name,
        velocity,
        completionRate,
        startDate: sprint.startDate,
        endDate: sprint.endDate,
      };
    });

    const velocities = sprintData.map(sprint => sprint.velocity);
    const averageVelocity = Math.round(velocities.reduce((sum, v) => sum + v, 0) / velocities.length);
    const highestVelocity = Math.max(...velocities);
    const lowestVelocity = Math.min(...velocities);

    const trend = velocities.length > 1 
      ? velocities[0] > velocities[velocities.length - 1] ? 'improving' : 'declining'
      : 'stable';

    const insights = [
      `Team completed ${completedSprints.length} sprints`,
      `Average velocity: ${averageVelocity} story points`,
      `Velocity range: ${lowestVelocity} - ${highestVelocity} points`,
    ];

    const recommendations = [
      averageVelocity < 20 ? 'Consider breaking down stories into smaller tasks' : '',
      trend === 'declining' ? 'Review team capacity and remove blockers' : '',
      'Track velocity consistently for better sprint planning',
    ].filter(rec => rec !== '');

    return {
      teamName: options.teamName || 'Default Team',
      sprintsAnalyzed: completedSprints.length,
      periodStart: completedSprints[completedSprints.length - 1].startDate,
      periodEnd: completedSprints[0].endDate,
      averageVelocity,
      highestVelocity,
      lowestVelocity,
      trend,
      sprintData,
      insights,
      recommendations,
    };
  }

  async getSprintStatus(sprintId?: string): Promise<SprintStatus> {
    let sprint: Sprint;
    
    if (sprintId) {
      if (!this.sprints.has(sprintId)) {
        throw new Error(`Sprint ${sprintId} not found`);
      }
      sprint = this.sprints.get(sprintId)!;
    } else {
      // Get current active sprint
      const activeSprints = Array.from(this.sprints.values())
        .filter(s => s.status === 'active')
        .sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
      
      if (activeSprints.length === 0) {
        throw new Error('No active sprint found');
      }
      
      sprint = activeSprints[0];
    }

    // Get full story details
    const sprintStories = sprint.storyIds
      .map(id => this.stories.get(id))
      .filter(story => story !== undefined) as Story[];

    // Get unique epic IDs from stories
    const epicIds = new Set<string>();
    sprintStories.forEach(story => {
      if (story.epic) {
        epicIds.add(story.epic);
      }
    });

    // Get full epic details
    const epics = Array.from(epicIds)
      .map(epicId => this.epics.get(epicId))
      .filter(epic => epic !== undefined) as Epic[];

    const totalStories = sprintStories.length;
    const completedStories = sprintStories.filter(story => story.status === 'done').length;
    const inProgressStories = sprintStories.filter(story => story.status === 'in_progress').length;
    const remainingStories = totalStories - completedStories - inProgressStories;

    const totalStoryPoints = sprintStories.reduce((sum, story) => sum + story.storyPoints, 0);
    const completedStoryPoints = sprintStories
      .filter(story => story.status === 'done')
      .reduce((sum, story) => sum + story.storyPoints, 0);

    const blockers = this.standups
      .filter(standup => standup.sprintId === sprint.id)
      .flatMap(standup => standup.blockers)
      .filter((blocker, index, arr) => arr.indexOf(blocker) === index);

    const daysElapsed = Math.ceil((new Date().getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24));
    const sprintDuration = Math.ceil((sprint.endDate.getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24));
    const daysRemaining = Math.max(0, sprintDuration - daysElapsed);
    const currentVelocity = daysElapsed > 0 ? Math.round(completedStoryPoints / daysElapsed) : 0;
    
    // Calculate burndown data
    const burndownData = this.calculateBurndownData(sprint, sprintStories);

    const completionPercentage = totalStories > 0 ? (completedStories / totalStories) * 100 : 0;
    const sprintHealth = completionPercentage > 75 ? 'Excellent' : 
                        completionPercentage > 50 ? 'Good' : 
                        completionPercentage > 25 ? 'At Risk' : 'Critical';

    // Team metrics
    const teamMembers = sprint.team || [];
    const teamCapacity = sprint.capacity || 0;
    const utilizationPercentage = teamCapacity > 0 ? (totalStoryPoints / teamCapacity) * 100 : 0;

    return {
      sprint,
      stories: sprintStories, // Full story objects
      epics, // Full epic objects
      totalStories,
      completedStories,
      inProgressStories,
      remainingStories,
      totalStoryPoints,
      completedStoryPoints,
      remainingStoryPoints: totalStoryPoints - completedStoryPoints,
      sprintHealth,
      blockers,
      currentVelocity,
      metrics: {
        velocity: sprint.velocity || currentVelocity,
        capacity: teamCapacity,
        utilization: utilizationPercentage,
        burndownData,
        health: sprintHealth.toLowerCase().replace(' ', '-') as 'excellent' | 'good' | 'at-risk' | 'critical',
        daysElapsed,
        daysRemaining,
        sprintDuration
      },
      team: {
        members: teamMembers,
        availability: teamMembers.length,
        blockers
      }
    };
  }

  private calculateBurndownData(sprint: Sprint, stories: Story[]): BurndownChart {
    const sprintDuration = Math.ceil((sprint.endDate.getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24));
    const totalStoryPoints = stories.reduce((sum, story) => sum + story.storyPoints, 0);
    
    const dailyData = [];
    const now = new Date();
    
    for (let day = 0; day <= sprintDuration; day++) {
      const dayDate = new Date(sprint.startDate.getTime() + day * 24 * 60 * 60 * 1000);
      
      if (dayDate > now) {
        // Future date - project ideal burndown
        const idealRemaining = totalStoryPoints * (1 - (day / sprintDuration));
        dailyData.push({
          day,
          date: dayDate,
          remainingPoints: Math.round(idealRemaining),
          completedPoints: totalStoryPoints - Math.round(idealRemaining),
          idealRemaining: Math.round(idealRemaining)
        });
      } else {
        // Past or current date - calculate actual
        const completedByDay = stories.filter(story => {
          return story.status === 'done' && 
                 story.completedAt && 
                 story.completedAt <= dayDate;
        }).reduce((sum, story) => sum + story.storyPoints, 0);
        
        dailyData.push({
          day,
          date: dayDate,
          remainingPoints: totalStoryPoints - completedByDay,
          completedPoints: completedByDay,
          idealRemaining: Math.round(totalStoryPoints * (1 - (day / sprintDuration)))
        });
      }
    }
    
    const currentDay = Math.min(
      Math.floor((now.getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24)),
      sprintDuration
    );
    
    const completedPoints = stories
      .filter(story => story.status === 'done')
      .reduce((sum, story) => sum + story.storyPoints, 0);
    
    const onTrack = completedPoints >= (totalStoryPoints * (currentDay / sprintDuration) * 0.9);
    
    return {
      sprintId: sprint.id,
      totalStoryPoints,
      completedPoints,
      sprintDays: sprintDuration,
      daysRemaining: Math.max(0, sprintDuration - currentDay),
      dailyData,
      onTrack,
      trend: onTrack ? 'on-track' : 'behind'
    };
  }

  async getBacklog(filter: BacklogFilter = {}): Promise<Story[]> {
    let backlogStories = Array.from(this.stories.values())
      .filter(story => !story.sprintId || story.sprintId === '');

    // Apply filters
    if (filter.epic) {
      backlogStories = backlogStories.filter(story => story.epic === filter.epic);
    }
    
    if (filter.priority) {
      backlogStories = backlogStories.filter(story => story.priority === filter.priority);
    }
    
    if (filter.assignee) {
      backlogStories = backlogStories.filter(story => story.assignee === filter.assignee);
    }
    
    if (filter.status) {
      backlogStories = backlogStories.filter(story => story.status === filter.status);
    }
    
    if (filter.maxStoryPoints) {
      backlogStories = backlogStories.filter(story => 
        story.storyPoints !== undefined && story.storyPoints <= filter.maxStoryPoints!
      );
    }

    // Sort by priority and creation date
    const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
    return backlogStories.sort((a, b) => {
      const priorityDiff = (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
      if (priorityDiff !== 0) return priorityDiff;
      return b.createdAt.getTime() - a.createdAt.getTime();
    });
  }

  async getSprints(filter?: { status?: string; includeCompleted?: boolean }): Promise<Sprint[]> {
    let sprints = Array.from(this.sprints.values());
    
    // Apply filters
    if (filter?.status) {
      sprints = sprints.filter(sprint => sprint.status === filter.status);
    }
    
    if (filter?.includeCompleted === false) {
      sprints = sprints.filter(sprint => sprint.status !== 'completed');
    }
    
    // Sort by start date (most recent first)
    return sprints.sort((a, b) => b.startDate.getTime() - a.startDate.getTime());
  }

  async getSprint(sprintId: string): Promise<Sprint | null> {
    const sprint = this.sprints.get(sprintId);
    return sprint || null;
  }

  async getActiveSprint(): Promise<Sprint | null> {
    const sprints = Array.from(this.sprints.values());
    const activeSprint = sprints.find(sprint => sprint.status === 'active');
    return activeSprint || null;
  }

  async getEpics(filter?: { status?: string; owner?: string }): Promise<Epic[]> {
    let epics = Array.from(this.epics.values());
    
    // Apply filters
    if (filter?.status) {
      epics = epics.filter(epic => epic.status === filter.status);
    }
    
    if (filter?.owner) {
      epics = epics.filter(epic => epic.owner === filter.owner);
    }
    
    // Sort by priority and creation date
    const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
    return epics.sort((a, b) => {
      const priorityDiff = (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
      if (priorityDiff !== 0) return priorityDiff;
      return b.createdAt.getTime() - a.createdAt.getTime();
    });
  }

  async getEpic(epicId: string): Promise<Epic | null> {
    const epic = this.epics.get(epicId);
    return epic || null;
  }

  async getStory(storyId: string): Promise<Story | null> {
    const story = this.stories.get(storyId);
    return story || null;
  }

  async getAllStories(): Promise<Story[]> {
    return Array.from(this.stories.values());
  }

  async getStoriesForSprint(sprintId: string): Promise<Story[]> {
    return Array.from(this.stories.values())
      .filter(story => story.sprintId === sprintId);
  }

  async getEpicsForSprint(sprintId: string): Promise<Epic[]> {
    const sprint = this.sprints.get(sprintId);
    if (!sprint) {
      return [];
    }

    const epicIds = new Set<string>();
    for (const storyId of sprint.storyIds) {
      const story = this.stories.get(storyId);
      if (story && story.epic) {
        epicIds.add(story.epic);
      }
    }

    const epics: Epic[] = [];
    for (const epicId of epicIds) {
      const epic = this.epics.get(epicId);
      if (epic) {
        epics.push(epic);
      }
    }

    return epics;
  }

  async getStoriesForEpicInSprint(epicId: string, sprintId: string): Promise<Story[]> {
    const sprint = this.sprints.get(sprintId);
    if (!sprint) {
      return [];
    }

    const stories: Story[] = [];
    for (const storyId of sprint.storyIds) {
      const story = this.stories.get(storyId);
      if (story && story.epic === epicId) {
        stories.push(story);
      }
    }

    return stories;
  }

  async getSprintProgress(sprintId: string): Promise<{ epicProgress: Map<string, { total: number; completed: number }> }> {
    const sprint = this.sprints.get(sprintId);
    if (!sprint) {
      return { epicProgress: new Map() };
    }

    const epicProgress = new Map<string, { total: number; completed: number }>();

    for (const storyId of sprint.storyIds) {
      const story = this.stories.get(storyId);
      if (story && story.epic) {
        const current = epicProgress.get(story.epic) || { total: 0, completed: 0 };
        current.total += story.storyPoints;
        if (story.status === 'done') {
          current.completed += story.storyPoints;
        }
        epicProgress.set(story.epic, current);
      }
    }

    return { epicProgress };
  }

  async addEpicToSprint(epicId: string, sprintId: string): Promise<void> {
    const sprint = this.sprints.get(sprintId);
    const epic = this.epics.get(epicId);
    
    if (!sprint) {
      throw new Error(`Sprint ${sprintId} not found`);
    }
    
    if (!epic) {
      throw new Error(`Epic ${epicId} not found`);
    }
    
    if (!sprint.epicIds) {
      sprint.epicIds = [];
    }
    
    if (!sprint.epicIds.includes(epicId)) {
      sprint.epicIds.push(epicId);
      this.sprints.set(sprintId, sprint);
      await this.saveSprints();
    }
  }

  async removeEpicFromSprint(epicId: string, sprintId: string): Promise<void> {
    const sprint = this.sprints.get(sprintId);
    
    if (!sprint) {
      throw new Error(`Sprint ${sprintId} not found`);
    }
    
    if (sprint.epicIds) {
      sprint.epicIds = sprint.epicIds.filter(id => id !== epicId);
      this.sprints.set(sprintId, sprint);
      await this.saveSprints();
    }
  }

  async getEpicsForSprintDirect(sprintId: string): Promise<Epic[]> {
    const sprint = this.sprints.get(sprintId);
    if (!sprint || !sprint.epicIds) {
      return [];
    }
    
    const epics: Epic[] = [];
    for (const epicId of sprint.epicIds) {
      const epic = this.epics.get(epicId);
      if (epic) {
        epics.push(epic);
      }
    }
    
    return epics;
  }

  private async saveSprints(): Promise<void> {
    const sprintsPath = path.join(this.agileDataPath, 'sprints.json');
    await fs.mkdir(path.dirname(sprintsPath), { recursive: true });
    await fs.writeFile(sprintsPath, JSON.stringify(Array.from(this.sprints.values()), null, 2));
  }

  private async saveStories(): Promise<void> {
    const storiesPath = path.join(this.agileDataPath, 'stories.json');
    await fs.mkdir(path.dirname(storiesPath), { recursive: true });
    await fs.writeFile(storiesPath, JSON.stringify(Array.from(this.stories.values()), null, 2));
  }

  private async saveEpics(): Promise<void> {
    const epicsPath = path.join(this.agileDataPath, 'epics.json');
    await fs.mkdir(path.dirname(epicsPath), { recursive: true });
    await fs.writeFile(epicsPath, JSON.stringify(Array.from(this.epics.values()), null, 2));
  }

  private async saveStandups(): Promise<void> {
    const standupsPath = path.join(this.agileDataPath, 'standups.json');
    await fs.mkdir(path.dirname(standupsPath), { recursive: true });
    await fs.writeFile(standupsPath, JSON.stringify(this.standups, null, 2));
  }

  private async saveRetrospectives(): Promise<void> {
    const retrospectivesPath = path.join(this.agileDataPath, 'retrospectives.json');
    await fs.mkdir(path.dirname(retrospectivesPath), { recursive: true });
    await fs.writeFile(retrospectivesPath, JSON.stringify(this.retrospectives, null, 2));
  }

  private generateId(): string {
    return `${Date.now().toString(16)}-${Math.random().toString(16).substring(2, 10)}`;
  }

  private async createSampleData(): Promise<void> {
    console.log('🔧 Creating sample agile data for dashboard');
    
    try {
      // Create sample epic
      const epic = await this.createEpic({
        title: 'User Authentication System',
        description: 'Implement comprehensive user authentication including login, registration, password reset, and session management',
        goals: [
          'Secure user registration and login',
          'Session management with JWT tokens', 
          'Password reset functionality',
          'Multi-factor authentication support'
        ],
        priority: 'high',
        owner: 'Product Team'
      });

      // Create sample sprint
      const sprint = await this.createSprint({
        name: 'Sprint 1 - Authentication Foundation',
        goal: 'Build core authentication infrastructure and user login flow',
        duration: 14,
        startDate: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000), // Started 5 days ago
        team: ['Alice Johnson', 'Bob Chen', 'Carol Davis']
      });

      // Update sprint to active status
      await this.updateSprint(sprint.id, { 
        status: 'active',
        capacity: 25
      });

      // Create sample stories
      const stories = [
        {
          title: 'User Registration API',
          description: 'Create REST API endpoints for user registration with email validation',
          storyPoints: 8,
          priority: 'high' as const,
          status: 'done' as const,
          acceptanceCriteria: [
            'API accepts email, password, and basic profile info',
            'Email validation is performed',
            'Password meets security requirements',
            'User account is created in database',
            'Confirmation email is sent'
          ],
          assignee: 'Alice Johnson',
          epic: epic.id,
          sprintId: sprint.id,
          tags: ['backend', 'api', 'security']
        },
        {
          title: 'Login Form UI',
          description: 'Design and implement responsive login form with validation',
          storyPoints: 5,
          priority: 'high' as const,
          status: 'in_progress' as const,
          acceptanceCriteria: [
            'Responsive design works on mobile and desktop',
            'Real-time validation feedback',
            'Clear error messages',
            'Remember me checkbox',
            'Forgot password link'
          ],
          assignee: 'Bob Chen',
          epic: epic.id,
          sprintId: sprint.id,
          tags: ['frontend', 'ui', 'forms']
        },
        {
          title: 'JWT Token Management',
          description: 'Implement JWT token generation, validation, and refresh logic',
          storyPoints: 8,
          priority: 'high' as const,
          status: 'review' as const,
          acceptanceCriteria: [
            'Tokens are generated on successful login',
            'Token expiration is handled',
            'Refresh token functionality',
            'Token validation middleware',
            'Secure token storage'
          ],
          assignee: 'Carol Davis',
          epic: epic.id,
          sprintId: sprint.id,
          tags: ['backend', 'security', 'authentication']
        },
        {
          title: 'Password Reset Flow',
          description: 'Build password reset functionality with secure email tokens',
          storyPoints: 5,
          priority: 'medium' as const,
          status: 'todo' as const,
          acceptanceCriteria: [
            'Password reset email generation',
            'Secure reset token creation',
            'Reset form with validation',
            'Token expiration handling',
            'Success confirmation'
          ],
          assignee: 'Alice Johnson',
          epic: epic.id,
          sprintId: sprint.id,
          tags: ['backend', 'email', 'security']
        }
      ];

      // Add stories to the system
      for (const storyData of stories) {
        await this.addStory(storyData);
      }

      console.log('✅ Sample agile data created successfully');
      
    } catch (error) {
      console.error('❌ Error creating sample data:', error);
    }
  }

  // Version management methods
  private async getDataVersion(): Promise<string> {
    try {
      const versionPath = path.join(this.agileDataPath, 'version.json');
      if (await this.fileExists(versionPath)) {
        const versionData = JSON.parse(await fs.readFile(versionPath, 'utf-8'));
        return versionData.version || '1.0.0';
      }
    } catch (error) {
      console.error('Error reading version file:', error);
    }
    return '1.0.0'; // Default to 1.0.0 if no version file exists
  }

  private compareVersions(v1: string, v2: string): number {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);
    
    for (let i = 0; i < 3; i++) {
      const part1 = parts1[i] || 0;
      const part2 = parts2[i] || 0;
      
      if (part1 > part2) return 1;
      if (part1 < part2) return -1;
    }
    
    return 0;
  }

  private async updateDataVersion(version: string): Promise<void> {
    // Ensure the agile data directory exists
    await fs.mkdir(this.agileDataPath, { recursive: true });
    
    const versionPath = path.join(this.agileDataPath, 'version.json');
    const versionData = {
      version,
      lastMigration: new Date().toISOString(),
      migrationHistory: []
    };
    
    // Try to preserve migration history
    try {
      if (await this.fileExists(versionPath)) {
        const existing = JSON.parse(await fs.readFile(versionPath, 'utf-8'));
        if (existing.migrationHistory) {
          versionData.migrationHistory = existing.migrationHistory;
        }
      }
    } catch (error) {
      // Ignore errors reading existing version file
    }
    
    // Add this migration to history
    versionData.migrationHistory.push({
      fromVersion: await this.getDataVersion(),
      toVersion: version,
      date: new Date().toISOString()
    });
    
    await fs.writeFile(versionPath, JSON.stringify(versionData, null, 2));
  }
}