import { inject, injectable } from 'tsyringe';
import { OpenAIService } from './openai.service';
import { LangChainService } from './langchain.service';
import { NLPService } from '../nlp/nlp.service';
import { TaskAIContextEntity, AIContextType, AIAgentType } from '../../entities/task-ai-context.entity';
import { getRepository } from 'typeorm';
import { Redis } from 'ioredis';
import { RateLimiterRedis } from 'rate-limiter-flexible';
import { getAIConfig, AIConfig } from '../../config/ai.config';

/**
 * Natural language task creation request
 */
export interface NaturalLanguageTaskRequest {
  prompt: string;
  projectId?: string;
  context?: {
    currentTasks?: string[];
    completedTasks?: string[];
    projectDescription?: string;
    userRole?: string;
  };
}

/**
 * AI-generated task structure
 */
export interface AIGeneratedTask {
  title: string;
  description: string;
  priority: 'low' | 'medium' | 'high' | 'critical';
  complexity: 'trivial' | 'minor' | 'major' | 'critical';
  estimatedHours?: number;
  acceptanceCriteria?: string[];
  tags?: string[];
  subtasks?: Array<{
    title: string;
    description: string;
    estimatedHours?: number;
  }>;
  dependencies?: string[];
  assignee?: string;
}

/**
 * Task suggestion response
 */
export interface TaskSuggestion {
  id: string;
  title: string;
  description: string;
  rationale: string;
  priority: 'low' | 'medium' | 'high' | 'critical';
  estimatedHours?: number;
  confidence: number;
}

/**
 * Cache key generators
 */
const CACHE_KEYS = {
  taskSuggestion: (projectId: string) => `ai:suggestions:${projectId}`,
  taskBreakdown: (taskId: string) => `ai:breakdown:${taskId}`,
  nlpTask: (promptHash: string) => `ai:nlp:${promptHash}`,
};

/**
 * Service for AI-powered task management
 * Integrates OpenAI and LangChain for intelligent task operations
 */
@injectable()
export class AITaskService {
  private config: AIConfig;
  private redis?: Redis;
  private rateLimiter?: RateLimiterRedis;
  private aiContextRepository = getRepository(TaskAIContextEntity);

  constructor(
    @inject('OpenAIService') private openAIService: OpenAIService,
    @inject('LangChainService') private langChainService: LangChainService,
    @inject('NLPService') private nlpService: NLPService,
    @inject('Redis') redis?: Redis,
  ) {
    this.config = getAIConfig();
    this.redis = redis;
    this.initializeRateLimiter();
  }

  /**
   * Initialize rate limiter for API calls
   */
  private initializeRateLimiter(): void {
    if (this.redis) {
      this.rateLimiter = new RateLimiterRedis({
        storeClient: this.redis,
        keyPrefix: 'ai_rate_limit',
        points: this.config.rateLimit.maxRequestsPerMinute,
        duration: 60, // 1 minute
        blockDuration: 10, // Block for 10 seconds
      });
    }
  }

  /**
   * Create a task from natural language input
   * @param request Natural language task request
   * @returns AI-generated task
   */
  async createTaskFromNaturalLanguage(
    request: NaturalLanguageTaskRequest,
  ): Promise<AIGeneratedTask> {
    // Check rate limit
    await this.checkRateLimit('nlp_task_creation');

    // Check cache
    const cacheKey = CACHE_KEYS.nlpTask(this.hashString(request.prompt));
    const cached = await this.getCached<AIGeneratedTask>(cacheKey);
    if (cached) {
      return cached;
    }

    const startTime = Date.now();

    try {
      // First, process through NLP pipeline for structured extraction
      const nlpResult = await this.nlpService.processTextWithTranslation({
        text: request.prompt,
        context: request.context,
        projectId: request.projectId,
        userId: request.context?.userRole,
      });

      // Build initial task from NLP extraction
      let task: AIGeneratedTask;
      
      if (nlpResult.success && nlpResult.structuredData) {
        // Use NLP-extracted data as base
        task = {
          title: nlpResult.entities.title || nlpResult.structuredData.title || '',
          description: nlpResult.entities.description || nlpResult.structuredData.description || request.prompt,
          priority: nlpResult.entities.priority || 'medium',
          complexity: this.mapComplexityFromNLP(nlpResult.classification.complexity),
          estimatedHours: nlpResult.entities.estimatedHours,
          acceptanceCriteria: nlpResult.entities.acceptanceCriteria || [],
          tags: nlpResult.entities.tags || [],
          dependencies: nlpResult.entities.dependencies || [],
          assignee: nlpResult.entities.assignees?.[0],
          subtasks: [],
        };

        // If NLP extraction was incomplete, enhance with LangChain
        if (!task.title || !task.description || task.acceptanceCriteria.length === 0) {
          const projectContext = this.buildProjectContext(request.context);
          const langChainTask = await this.langChainService.createTaskFromNaturalLanguage(
            request.prompt,
            projectContext,
            { temperature: 0.5 },
          );
          
          // Merge LangChain results with NLP results
          task = {
            ...langChainTask,
            // Prefer NLP-extracted values when available
            priority: nlpResult.entities.priority || langChainTask.priority,
            estimatedHours: nlpResult.entities.estimatedHours || langChainTask.estimatedHours,
            tags: [...new Set([...(nlpResult.entities.tags || []), ...(langChainTask.tags || [])])],
            dependencies: [...new Set([...(nlpResult.entities.dependencies || []), ...(langChainTask.dependencies || [])])],
          };
        }
      } else {
        // Fallback to LangChain if NLP fails
        const projectContext = this.buildProjectContext(request.context);
        task = await this.langChainService.createTaskFromNaturalLanguage(
          request.prompt,
          projectContext,
          { temperature: 0.5 },
        );
      }

      // Enhance with OpenAI if needed for additional details
      if (!task.acceptanceCriteria || task.acceptanceCriteria.length === 0) {
        const criteriaPrompt = `Generate 3-5 acceptance criteria for this task: ${task.title}\nDescription: ${task.description}`;
        const criteriaResponse = await this.openAIService.complete(
          [{ role: 'user', content: criteriaPrompt }],
          { 
            systemPrompt: 'You are a technical project manager. Generate clear, testable acceptance criteria.',
            temperature: 0.3,
            maxTokens: 500,
          },
        );

        task.acceptanceCriteria = criteriaResponse.content
          .split('\n')
          .filter(line => line.trim())
          .map(line => line.replace(/^[\d\-\*\.]+\s*/, '').trim());
      }

      // Store AI context for auditing
      await this.saveAIContext({
        task_id: undefined, // Will be linked when task is created
        ai_agent_type: nlpResult?.success ? AIAgentType.NLP : AIAgentType.LANGCHAIN,
        context_type: AIContextType.PROMPT,
        context_data: {
          prompt: request.prompt,
          context: request.context,
          model: this.config.openai.model,
          nlpResult: nlpResult ? {
            success: nlpResult.success,
            language: nlpResult.language,
            confidence: nlpResult.entities.confidence,
            tokensUsed: nlpResult.tokensUsed,
          } : undefined,
        } as any,
        execution_timestamp: new Date(),
        success: true,
        tokens_used: (nlpResult?.tokensUsed || 0) + (task.executionTimeMs ? Math.ceil(task.executionTimeMs / 10) : 0),
        execution_time_ms: Date.now() - startTime,
      });

      // Cache the result
      await this.setCached(cacheKey, task, 3600); // 1 hour TTL

      return task;
    } catch (error) {
      // Log error context
      await this.saveAIContext({
        task_id: undefined,
        ai_agent_type: AIAgentType.LANGCHAIN,
        context_type: AIContextType.ERROR,
        context_data: {
          prompt: request.prompt,
          error: error instanceof Error ? error.message : 'Unknown error',
        } as any,
        execution_timestamp: new Date(),
        success: false,
        tokens_used: 0,
        execution_time_ms: Date.now() - startTime,
      });

      throw error;
    }
  }

  /**
   * Break down a complex task into subtasks
   * @param taskId Task ID to break down
   * @param taskDescription Task description
   * @param context Additional context
   * @returns Task breakdown with subtasks
   */
  async breakdownTask(
    taskId: string,
    taskDescription: string,
    context?: string,
  ): Promise<any> {
    // Check rate limit
    await this.checkRateLimit('task_breakdown');

    // Check cache
    const cacheKey = CACHE_KEYS.taskBreakdown(taskId);
    const cached = await this.getCached(cacheKey);
    if (cached) {
      return cached;
    }

    const startTime = Date.now();

    try {
      const breakdown = await this.langChainService.breakdownTask(
        taskDescription,
        context || '',
        { temperature: 0.3, maxTokens: 2000 },
      );

      // Store AI context
      await this.saveAIContext({
        task_id: taskId,
        ai_agent_type: AIAgentType.LANGCHAIN,
        context_type: AIContextType.RESULT,
        context_data: {
          operation: 'task_breakdown',
          result: breakdown,
        } as any,
        execution_timestamp: new Date(),
        success: true,
        tokens_used: breakdown.estimatedTotalHours ? breakdown.estimatedTotalHours * 10 : 100,
        execution_time_ms: Date.now() - startTime,
      });

      // Cache the result
      await this.setCached(cacheKey, breakdown, 7200); // 2 hour TTL

      return breakdown;
    } catch (error) {
      await this.saveAIContext({
        task_id: taskId,
        ai_agent_type: AIAgentType.LANGCHAIN,
        context_type: AIContextType.ERROR,
        context_data: {
          operation: 'task_breakdown',
          error: error instanceof Error ? error.message : 'Unknown error',
        } as any,
        execution_timestamp: new Date(),
        success: false,
        tokens_used: 0,
        execution_time_ms: Date.now() - startTime,
      });

      throw error;
    }
  }

  /**
   * Get AI-powered task suggestions for a project
   * @param projectId Project ID
   * @param currentContext Current project context
   * @returns List of task suggestions
   */
  async getTaskSuggestions(
    projectId: string,
    currentContext: {
      projectDescription: string;
      completedTasks: string[];
      currentTasks: string[];
      goals: string[];
    },
  ): Promise<TaskSuggestion[]> {
    // Check rate limit
    await this.checkRateLimit('task_suggestions');

    // Check cache
    const cacheKey = CACHE_KEYS.taskSuggestion(projectId);
    const cached = await this.getCached<TaskSuggestion[]>(cacheKey);
    if (cached) {
      return cached;
    }

    const startTime = Date.now();

    try {
      const suggestionsText = await this.langChainService.getTaskSuggestions(
        currentContext,
        { temperature: 0.7, maxTokens: 1500 },
      );

      // Parse suggestions into structured format
      const suggestions = this.parseSuggestions(suggestionsText);

      // Store AI context
      await this.saveAIContext({
        task_id: undefined,
        ai_agent_type: AIAgentType.LANGCHAIN,
        context_type: AIContextType.RESULT,
        context_data: {
          operation: 'task_suggestions',
          projectId,
          suggestions,
        } as any,
        execution_timestamp: new Date(),
        success: true,
        tokens_used: Math.ceil(suggestionsText.length / 4),
        execution_time_ms: Date.now() - startTime,
      });

      // Cache suggestions
      await this.setCached(cacheKey, suggestions, 1800); // 30 minutes TTL

      return suggestions;
    } catch (error) {
      await this.saveAIContext({
        task_id: undefined,
        ai_agent_type: AIAgentType.LANGCHAIN,
        context_type: AIContextType.ERROR,
        context_data: {
          operation: 'task_suggestions',
          projectId,
          error: error instanceof Error ? error.message : 'Unknown error',
        } as any,
        execution_timestamp: new Date(),
        success: false,
        tokens_used: 0,
        execution_time_ms: Date.now() - startTime,
      });

      throw error;
    }
  }

  /**
   * Update AI context for a task based on feedback
   * @param taskId Task ID
   * @param feedback User feedback
   */
  async updateTaskContext(taskId: string, feedback: string): Promise<void> {
    const startTime = Date.now();

    await this.saveAIContext({
      task_id: taskId,
      ai_agent_type: AIAgentType.OPENAI,
      context_type: AIContextType.FEEDBACK,
      context_data: {
        feedback,
        timestamp: new Date().toISOString(),
      } as any,
      execution_timestamp: new Date(),
      success: true,
      tokens_used: 0,
      execution_time_ms: Date.now() - startTime,
    });

    // Invalidate related caches
    await this.invalidateCache(`ai:breakdown:${taskId}`);
  }

  /**
   * Warm up cache with common queries
   * @param projectId Project ID to warm cache for
   */
  async warmCache(projectId: string): Promise<void> {
    // This method can be called during low-traffic periods
    // to pre-populate cache with likely queries
    
    const commonPrompts = [
      'Create a bug fix task',
      'Add a new feature',
      'Improve performance',
      'Write documentation',
    ];

    for (const prompt of commonPrompts) {
      try {
        await this.createTaskFromNaturalLanguage({
          prompt,
          projectId,
        });
      } catch (error) {
        console.warn(`Cache warming failed for prompt: ${prompt}`, error);
      }
    }
  }

  /**
   * Get AI service health status
   * @returns Health status of AI services
   */
  async getHealthStatus(): Promise<{
    openai: boolean;
    langchain: boolean;
    redis: boolean;
    rateLimiter: boolean;
  }> {
    const [openaiHealth, langchainHealth] = await Promise.all([
      this.openAIService.testConnection(),
      this.langChainService.testConnection(),
    ]);

    return {
      openai: openaiHealth,
      langchain: langchainHealth,
      redis: this.redis ? await this.testRedisConnection() : false,
      rateLimiter: !!this.rateLimiter,
    };
  }

  /**
   * Get usage statistics for AI services
   * @returns Combined usage stats
   */
  async getUsageStats(): Promise<{
    openai: any;
    totalContextsSaved: number;
    cacheHitRate?: number;
  }> {
    const openaiStats = this.openAIService.getUsageStats();
    const contextCount = await this.aiContextRepository.count();

    return {
      openai: openaiStats,
      totalContextsSaved: contextCount,
      cacheHitRate: this.redis ? await this.calculateCacheHitRate() : undefined,
    };
  }

  /**
   * Process task update with NLP understanding
   * @param taskId Task to update
   * @param updatePrompt Natural language update description
   * @returns Updated task information
   */
  async processTaskUpdate(
    taskId: string,
    updatePrompt: string,
  ): Promise<Partial<AIGeneratedTask>> {
    // Process update through NLP
    const nlpResult = await this.nlpService.processText({
      text: updatePrompt,
      context: { taskId, operation: 'update' },
    });

    const updates: Partial<AIGeneratedTask> = {};

    // Extract specific updates from NLP results
    if (nlpResult.entities.priority) {
      updates.priority = nlpResult.entities.priority;
    }
    if (nlpResult.entities.estimatedHours !== undefined) {
      updates.estimatedHours = nlpResult.entities.estimatedHours;
    }
    if (nlpResult.entities.assignees?.length) {
      updates.assignee = nlpResult.entities.assignees[0];
    }
    if (nlpResult.entities.tags?.length) {
      updates.tags = nlpResult.entities.tags;
    }
    if (nlpResult.entities.acceptanceCriteria?.length) {
      updates.acceptanceCriteria = nlpResult.entities.acceptanceCriteria;
    }

    // Store context for auditing
    await this.saveAIContext({
      task_id: taskId,
      ai_agent_type: AIAgentType.NLP,
      context_type: AIContextType.UPDATE,
      context_data: {
        updatePrompt,
        extractedUpdates: updates,
        nlpConfidence: nlpResult.entities.confidence,
      } as any,
      execution_timestamp: new Date(),
      success: true,
      tokens_used: nlpResult.tokensUsed || 0,
      execution_time_ms: nlpResult.processingTime,
    });

    return updates;
  }

  /**
   * Get task recommendations based on NLP analysis
   * @param projectId Project to analyze
   * @param recentActivity Recent project activity for context
   * @returns Task suggestions
   */
  async getTaskRecommendations(
    projectId: string,
    recentActivity?: string[],
  ): Promise<TaskSuggestion[]> {
    // Check cache
    const cacheKey = CACHE_KEYS.taskSuggestion(projectId);
    const cached = await this.getCached<TaskSuggestion[]>(cacheKey);
    if (cached) {
      return cached;
    }

    // Build context from recent activity
    const activityContext = recentActivity?.join('\n') || '';
    
    // Use LangChain to generate intelligent suggestions
    const suggestions = await this.langChainService.suggestNextTasks(
      projectId,
      activityContext,
      { temperature: 0.7, maxTokens: 1500 },
    );

    // Process each suggestion through NLP for enhanced extraction
    const enhancedSuggestions: TaskSuggestion[] = [];
    
    for (const suggestion of suggestions) {
      const nlpResult = await this.nlpService.processText({
        text: `${suggestion.title}: ${suggestion.description}`,
        projectId,
      });

      enhancedSuggestions.push({
        ...suggestion,
        priority: nlpResult.entities.priority || suggestion.priority || 'medium',
        estimatedHours: nlpResult.entities.estimatedHours || suggestion.estimatedHours,
        confidence: (suggestion.confidence + (nlpResult.entities.confidence || 0)) / 2,
      });
    }

    // Cache results
    await this.setCached(cacheKey, enhancedSuggestions, 1800); // 30 minutes

    return enhancedSuggestions;
  }

  /**
   * Validate task description using NLP
   * @param description Task description to validate
   * @returns Validation result with suggestions
   */
  async validateTaskDescription(description: string): Promise<{
    isValid: boolean;
    issues: string[];
    suggestions: string[];
    extractedInfo: any;
  }> {
    const nlpResult = await this.nlpService.processText({
      text: description,
    });

    const issues: string[] = [];
    const suggestions: string[] = [];

    // Check for completeness
    if (!nlpResult.entities.title && !nlpResult.structuredData?.title) {
      issues.push('No clear task title found');
      suggestions.push('Start with a clear, action-oriented title');
    }

    if (!nlpResult.entities.acceptanceCriteria?.length) {
      issues.push('No acceptance criteria specified');
      suggestions.push('Add clear, testable acceptance criteria');
    }

    if (!nlpResult.entities.estimatedHours) {
      suggestions.push('Consider adding a time estimate');
    }

    if (!nlpResult.entities.priority) {
      suggestions.push('Specify the task priority (low, medium, high, critical)');
    }

    // Check confidence
    if (nlpResult.entities.confidence && nlpResult.entities.confidence < 0.6) {
      issues.push('Description is ambiguous or unclear');
      suggestions.push('Provide more specific details about the task');
    }

    return {
      isValid: issues.length === 0,
      issues,
      suggestions,
      extractedInfo: {
        entities: nlpResult.entities,
        classification: nlpResult.classification,
      },
    };
  }

  // Helper methods

  /**
   * Map NLP complexity to task complexity
   */
  private mapComplexityFromNLP(nlpComplexity?: string): AIGeneratedTask['complexity'] {
    switch (nlpComplexity) {
      case 'trivial':
      case 'simple':
        return 'trivial';
      case 'moderate':
        return 'minor';
      case 'complex':
        return 'major';
      case 'very-complex':
        return 'critical';
      default:
        return 'minor';
    }
  }

  private buildProjectContext(context?: NaturalLanguageTaskRequest['context']): string {
    if (!context) return '';

    const parts: string[] = [];
    
    if (context.projectDescription) {
      parts.push(`Project: ${context.projectDescription}`);
    }
    if (context.currentTasks?.length) {
      parts.push(`Current tasks: ${context.currentTasks.join(', ')}`);
    }
    if (context.completedTasks?.length) {
      parts.push(`Completed tasks: ${context.completedTasks.join(', ')}`);
    }
    if (context.userRole) {
      parts.push(`User role: ${context.userRole}`);
    }

    return parts.join('\n');
  }

  private parseSuggestions(text: string): TaskSuggestion[] {
    const suggestions: TaskSuggestion[] = [];
    const lines = text.split('\n');
    
    let currentSuggestion: Partial<TaskSuggestion> | null = null;
    
    for (const line of lines) {
      const trimmed = line.trim();
      
      // Match numbered items (1. Title or 1) Title)
      const match = trimmed.match(/^(\d+)[.\)]\s*(.+)/);
      if (match) {
        if (currentSuggestion) {
          suggestions.push(this.finalizeSuggestion(currentSuggestion));
        }
        
        const [, number, titleAndDesc] = match;
        const [title, ...descParts] = titleAndDesc.split('-').map(s => s.trim());
        
        currentSuggestion = {
          id: `suggestion-${number}`,
          title,
          description: descParts.join(' - '),
          rationale: '',
          priority: 'medium',
          confidence: 0.7,
        };
      } else if (currentSuggestion && trimmed) {
        // Add to description or rationale
        if (trimmed.toLowerCase().includes('because') || 
            trimmed.toLowerCase().includes('rationale')) {
          currentSuggestion.rationale = trimmed;
        } else {
          currentSuggestion.description = 
            `${currentSuggestion.description} ${trimmed}`.trim();
        }
      }
    }
    
    if (currentSuggestion) {
      suggestions.push(this.finalizeSuggestion(currentSuggestion));
    }
    
    return suggestions;
  }

  private finalizeSuggestion(partial: Partial<TaskSuggestion>): TaskSuggestion {
    return {
      id: partial.id || `suggestion-${Date.now()}`,
      title: partial.title || 'Untitled Task',
      description: partial.description || '',
      rationale: partial.rationale || 'AI-generated suggestion based on project context',
      priority: partial.priority || 'medium',
      estimatedHours: partial.estimatedHours,
      confidence: partial.confidence || 0.5,
    };
  }

  private async saveAIContext(context: Partial<TaskAIContextEntity>): Promise<void> {
    try {
      const aiContext = this.aiContextRepository.create(context);
      await this.aiContextRepository.save(aiContext);
    } catch (error) {
      console.error('Failed to save AI context:', error);
      // Don't throw - this is auxiliary functionality
    }
  }

  private async checkRateLimit(operation: string): Promise<void> {
    if (!this.rateLimiter) return;

    try {
      await this.rateLimiter.consume(operation);
    } catch (error) {
      throw new Error(`Rate limit exceeded for ${operation}. Please try again later.`);
    }
  }

  private async getCached<T>(key: string): Promise<T | null> {
    if (!this.redis) return null;

    try {
      const cached = await this.redis.get(key);
      return cached ? JSON.parse(cached) : null;
    } catch (error) {
      console.warn('Cache get failed:', error);
      return null;
    }
  }

  private async setCached(key: string, value: any, ttl: number): Promise<void> {
    if (!this.redis) return;

    try {
      await this.redis.set(key, JSON.stringify(value), 'EX', ttl);
    } catch (error) {
      console.warn('Cache set failed:', error);
    }
  }

  private async invalidateCache(pattern: string): Promise<void> {
    if (!this.redis) return;

    try {
      const keys = await this.redis.keys(pattern);
      if (keys.length > 0) {
        await this.redis.del(...keys);
      }
    } catch (error) {
      console.warn('Cache invalidation failed:', error);
    }
  }

  private hashString(str: string): string {
    // Simple hash for cache keys
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash).toString(36);
  }

  private async testRedisConnection(): Promise<boolean> {
    if (!this.redis) return false;
    
    try {
      await this.redis.ping();
      return true;
    } catch {
      return false;
    }
  }

  private async calculateCacheHitRate(): Promise<number> {
    // This would typically track hits/misses over time
    // For now, return a placeholder
    return 0.0;
  }
}