import { promises as fs } from 'fs';
import { join } from 'path';
import { 
  ProductRequirement, 
  CreateRequirementOptions, 
  UpdateRequirementOptions,
  RequirementFilter,
  RequirementSearchOptions,
  RequirementStoryLink,
  RequirementsReport,
  ImplementationStatusUpdate,
  RequirementChange
} from './types.js';

export class ProductRequirementsStore {
  private requirementsPath: string;
  private linksPath: string;
  private changesPath: string;

  constructor(dataDir: string = '.atlas') {
    this.requirementsPath = join(dataDir, 'requirements.json');
    this.linksPath = join(dataDir, 'requirement-links.json');
    this.changesPath = join(dataDir, 'requirement-changes.json');
  }

  private async ensureDataDir(): Promise<void> {
    try {
      await fs.mkdir('.atlas', { recursive: true });
    } catch (error) {
      // Directory might already exist
    }
  }

  private async loadRequirements(): Promise<ProductRequirement[]> {
    try {
      const data = await fs.readFile(this.requirementsPath, 'utf-8');
      return JSON.parse(data);
    } catch (error) {
      return [];
    }
  }

  private async saveRequirements(requirements: ProductRequirement[]): Promise<void> {
    await this.ensureDataDir();
    await fs.writeFile(this.requirementsPath, JSON.stringify(requirements, null, 2));
  }

  private async loadLinks(): Promise<RequirementStoryLink[]> {
    try {
      const data = await fs.readFile(this.linksPath, 'utf-8');
      return JSON.parse(data);
    } catch (error) {
      return [];
    }
  }

  private async saveLinks(links: RequirementStoryLink[]): Promise<void> {
    await this.ensureDataDir();
    await fs.writeFile(this.linksPath, JSON.stringify(links, null, 2));
  }

  private async loadChanges(): Promise<RequirementChange[]> {
    try {
      const data = await fs.readFile(this.changesPath, 'utf-8');
      return JSON.parse(data);
    } catch (error) {
      return [];
    }
  }

  private async saveChanges(changes: RequirementChange[]): Promise<void> {
    await this.ensureDataDir();
    await fs.writeFile(this.changesPath, JSON.stringify(changes, null, 2));
  }

  private async recordChange(requirementId: string, field: string, oldValue: any, newValue: any): Promise<void> {
    const changes = await this.loadChanges();
    changes.push({
      requirement_id: requirementId,
      field,
      old_value: oldValue,
      new_value: newValue,
      changed_by: 'system',
      changed_at: new Date().toISOString()
    });
    await this.saveChanges(changes);
  }

  async createRequirement(options: CreateRequirementOptions): Promise<ProductRequirement> {
    const requirements = await this.loadRequirements();
    
    const requirement: ProductRequirement = {
      id: `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      name: options.name,
      type: options.type,
      description: options.description,
      acceptance_criteria: options.acceptance_criteria || [],
      priority: options.priority || 'medium',
      status: options.status || 'draft',
      repositories: options.repositories || [],
      related_stories: options.related_stories || [],
      tags: options.tags || [],
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString(),
      version: 1,
      business_value: options.business_value,
      technical_notes: options.technical_notes,
      test_criteria: options.test_criteria || [],
      compliance_requirements: options.compliance_requirements || [],
      estimated_effort: options.estimated_effort,
      target_release: options.target_release,
      parent_requirement_id: options.parent_requirement_id,
      child_requirement_ids: []
    };

    // Update parent's child list if this is a child requirement
    if (options.parent_requirement_id) {
      const parentIndex = requirements.findIndex(r => r.id === options.parent_requirement_id);
      if (parentIndex !== -1) {
        requirements[parentIndex].child_requirement_ids = requirements[parentIndex].child_requirement_ids || [];
        requirements[parentIndex].child_requirement_ids.push(requirement.id);
      }
    }

    requirements.push(requirement);
    await this.saveRequirements(requirements);
    
    return requirement;
  }

  async getRequirement(id: string): Promise<ProductRequirement | null> {
    const requirements = await this.loadRequirements();
    return requirements.find(r => r.id === id) || null;
  }

  async updateRequirement(options: UpdateRequirementOptions): Promise<ProductRequirement | null> {
    const requirements = await this.loadRequirements();
    const index = requirements.findIndex(r => r.id === options.id);
    
    if (index === -1) {
      return null;
    }

    const oldRequirement = { ...requirements[index] };
    const updatedRequirement = { ...requirements[index] };

    // Track changes for each modified field
    const fieldsToUpdate = Object.keys(options).filter(key => key !== 'id');
    
    for (const field of fieldsToUpdate) {
      const oldValue = (oldRequirement as any)[field];
      const newValue = (options as any)[field];
      
      if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
        (updatedRequirement as any)[field] = newValue;
        await this.recordChange(options.id, field, oldValue, newValue);
      }
    }

    updatedRequirement.updated_at = new Date().toISOString();
    updatedRequirement.version = (updatedRequirement.version || 1) + 1;

    requirements[index] = updatedRequirement;
    await this.saveRequirements(requirements);
    
    return updatedRequirement;
  }

  async deleteRequirement(id: string): Promise<boolean> {
    const requirements = await this.loadRequirements();
    const index = requirements.findIndex(r => r.id === id);
    
    if (index === -1) {
      return false;
    }

    const requirement = requirements[index];

    // Remove from parent's child list if this is a child requirement
    if (requirement.parent_requirement_id) {
      const parentIndex = requirements.findIndex(r => r.id === requirement.parent_requirement_id);
      if (parentIndex !== -1 && requirements[parentIndex].child_requirement_ids) {
        requirements[parentIndex].child_requirement_ids = 
          requirements[parentIndex].child_requirement_ids!.filter(childId => childId !== id);
      }
    }

    // Remove all links for this requirement
    const links = await this.loadLinks();
    const updatedLinks = links.filter(l => l.requirement_id !== id);
    await this.saveLinks(updatedLinks);

    requirements.splice(index, 1);
    await this.saveRequirements(requirements);
    
    return true;
  }

  async listRequirements(filter?: RequirementFilter): Promise<ProductRequirement[]> {
    const requirements = await this.loadRequirements();
    
    if (!filter) {
      return requirements;
    }

    return requirements.filter(req => {
      if (filter.type && req.type !== filter.type) return false;
      if (filter.status && req.status !== filter.status) return false;
      if (filter.priority && req.priority !== filter.priority) return false;
      if (filter.repository) {
        const hasRepo = req.repositories?.some(r => r.name === filter.repository);
        if (!hasRepo) return false;
      }
      if (filter.tag) {
        const hasTag = req.tags?.includes(filter.tag);
        if (!hasTag) return false;
      }
      if (filter.parent_id && req.parent_requirement_id !== filter.parent_id) return false;
      if (filter.has_stories !== undefined) {
        const hasStories = req.related_stories && req.related_stories.length > 0;
        if (filter.has_stories !== hasStories) return false;
      }
      if (filter.implementation_status) {
        const hasStatus = req.repositories?.some(r => r.implementation_status === filter.implementation_status);
        if (!hasStatus) return false;
      }
      return true;
    });
  }

  async searchRequirements(options: RequirementSearchOptions): Promise<ProductRequirement[]> {
    const requirements = await this.loadRequirements();
    const query = options.query.toLowerCase();
    const searchFields = options.search_in || ['name', 'description'];
    
    let results = requirements.filter(req => {
      for (const field of searchFields) {
        switch (field) {
          case 'name':
            if (req.name.toLowerCase().includes(query)) return true;
            break;
          case 'description':
            if (req.description.toLowerCase().includes(query)) return true;
            break;
          case 'acceptance_criteria':
            if (req.acceptance_criteria?.some(ac => ac.toLowerCase().includes(query))) return true;
            break;
          case 'tags':
            if (req.tags?.some(tag => tag.toLowerCase().includes(query))) return true;
            break;
        }
      }
      return false;
    });

    // Apply additional filters if provided
    if (options.filter) {
      results = results.filter(req => {
        const filter = options.filter!;
        if (filter.type && req.type !== filter.type) return false;
        if (filter.status && req.status !== filter.status) return false;
        if (filter.priority && req.priority !== filter.priority) return false;
        return true;
      });
    }

    // Apply pagination
    const offset = options.offset || 0;
    const limit = options.limit || results.length;
    
    return results.slice(offset, offset + limit);
  }

  async linkRequirementToStory(requirementId: string, storyId: string, linkType?: string, notes?: string): Promise<RequirementStoryLink> {
    const requirements = await this.loadRequirements();
    const requirement = requirements.find(r => r.id === requirementId);
    
    if (!requirement) {
      throw new Error(`Requirement ${requirementId} not found`);
    }

    // Update requirement's related stories
    if (!requirement.related_stories) {
      requirement.related_stories = [];
    }
    if (!requirement.related_stories.includes(storyId)) {
      requirement.related_stories.push(storyId);
      await this.saveRequirements(requirements);
    }

    // Create link record
    const links = await this.loadLinks();
    const link: RequirementStoryLink = {
      requirement_id: requirementId,
      story_id: storyId,
      link_type: linkType as any || 'implements',
      notes,
      created_at: new Date().toISOString()
    };
    
    links.push(link);
    await this.saveLinks(links);
    
    return link;
  }

  async getRequirementsByStory(storyId: string): Promise<ProductRequirement[]> {
    const links = await this.loadLinks();
    const requirementIds = links
      .filter(l => l.story_id === storyId)
      .map(l => l.requirement_id);
    
    const requirements = await this.loadRequirements();
    return requirements.filter(r => requirementIds.includes(r.id));
  }

  async getStoriesByRequirement(requirementId: string): Promise<string[]> {
    const requirement = await this.getRequirement(requirementId);
    return requirement?.related_stories || [];
  }

  async updateImplementationStatus(update: ImplementationStatusUpdate): Promise<ProductRequirement | null> {
    const requirements = await this.loadRequirements();
    const requirement = requirements.find(r => r.id === update.requirement_id);
    
    if (!requirement) {
      return null;
    }

    if (!requirement.repositories) {
      requirement.repositories = [];
    }

    const repoIndex = requirement.repositories.findIndex(r => r.name === update.repository_name);
    
    if (repoIndex === -1) {
      // Add new repository
      requirement.repositories.push({
        name: update.repository_name,
        implementation_status: update.status,
        branch: update.branch,
        pr_url: update.pr_url,
        notes: update.notes
      });
    } else {
      // Update existing repository
      requirement.repositories[repoIndex] = {
        ...requirement.repositories[repoIndex],
        implementation_status: update.status,
        branch: update.branch || requirement.repositories[repoIndex].branch,
        pr_url: update.pr_url || requirement.repositories[repoIndex].pr_url,
        notes: update.notes || requirement.repositories[repoIndex].notes
      };
    }

    requirement.updated_at = new Date().toISOString();
    await this.saveRequirements(requirements);
    
    return requirement;
  }

  async getRequirementsByStatus(status: ProductRequirement['status']): Promise<ProductRequirement[]> {
    const requirements = await this.loadRequirements();
    return requirements.filter(r => r.status === status);
  }

  async generateRequirementsReport(options?: {
    format?: 'json' | 'markdown' | 'html';
    include_implementation_status?: boolean;
    filter?: RequirementFilter;
    group_by?: 'type' | 'status' | 'priority';
  }): Promise<RequirementsReport> {
    const requirements = options?.filter 
      ? await this.listRequirements(options.filter)
      : await this.loadRequirements();

    const summary = {
      total_requirements: requirements.length,
      by_type: {} as Record<string, number>,
      by_status: {} as Record<string, number>,
      by_priority: {} as Record<string, number>,
      implementation_progress: {
        not_started: 0,
        in_progress: 0,
        completed: 0,
        blocked: 0
      }
    };

    // Calculate summary statistics
    for (const req of requirements) {
      // By type
      summary.by_type[req.type] = (summary.by_type[req.type] || 0) + 1;
      
      // By status
      summary.by_status[req.status!] = (summary.by_status[req.status!] || 0) + 1;
      
      // By priority
      summary.by_priority[req.priority!] = (summary.by_priority[req.priority!] || 0) + 1;
      
      // Implementation progress
      if (req.repositories && req.repositories.length > 0) {
        // Check if all repos are completed
        const allCompleted = req.repositories.every(r => r.implementation_status === 'completed');
        const anyInProgress = req.repositories.some(r => r.implementation_status === 'in_progress');
        const anyBlocked = req.repositories.some(r => r.implementation_status === 'blocked');
        
        if (allCompleted) {
          summary.implementation_progress.completed++;
        } else if (anyBlocked) {
          summary.implementation_progress.blocked++;
        } else if (anyInProgress) {
          summary.implementation_progress.in_progress++;
        } else {
          summary.implementation_progress.not_started++;
        }
      } else {
        summary.implementation_progress.not_started++;
      }
    }

    // Sort requirements based on group_by option
    let sortedRequirements = [...requirements];
    if (options?.group_by) {
      sortedRequirements.sort((a, b) => {
        const aValue = a[options.group_by as keyof ProductRequirement] as string;
        const bValue = b[options.group_by as keyof ProductRequirement] as string;
        return aValue.localeCompare(bValue);
      });
    }

    return {
      summary,
      requirements: sortedRequirements,
      generated_at: new Date().toISOString(),
      format: options?.format || 'json'
    };
  }
}