import { injectable } from 'tsyringe';
import { z } from 'zod';
import { OpenAIService } from '../ai/openai.service';
import { LangChainService } from '../ai/langchain.service';
import { TextParserService } from './parsers/text-parser.service';
import { EntityExtractorService } from './extractors/entity-extractor.service';
import { TaskClassifierService } from './classifiers/task-classifier.service';
import { StructuredDataGeneratorService } from './generators/structured-data.service';
import { LanguageDetectorService } from './language-detector.service';
import { TranslationService } from './translation.service';

/**
 * NLP Pipeline Configuration
 */
export const NLPConfigSchema = z.object({
  enableCaching: z.boolean().default(true),
  cacheMaxAge: z.number().default(3600000), // 1 hour
  maxTokensPerRequest: z.number().default(2000),
  defaultLanguage: z.string().default('en'),
  supportedLanguages: z.array(z.string()).default(['en', 'es', 'fr', 'de']),
  confidenceThreshold: z.number().min(0).max(1).default(0.7),
});

export type NLPConfig = z.infer<typeof NLPConfigSchema>;

/**
 * Core NLP interfaces
 */
export interface NLPInput {
  text: string;
  language?: string;
  context?: Record<string, any>;
  projectId?: string;
  userId?: string;
}

export interface ExtractedEntities {
  title?: string;
  description?: string;
  priority?: 'critical' | 'high' | 'medium' | 'low';
  deadline?: Date;
  estimatedHours?: number;
  assignees?: string[];
  dependencies?: string[];
  tags?: string[];
  acceptanceCriteria?: string[];
  mentions?: string[];
  technologies?: string[];
  confidence?: number;
}

export interface TaskClassification {
  type?: 'feature' | 'bug' | 'improvement' | 'task' | 'documentation' | 'test';
  urgency?: 'immediate' | 'urgent' | 'normal' | 'low';
  sentiment?: 'positive' | 'neutral' | 'negative';
  complexity?: 'trivial' | 'simple' | 'moderate' | 'complex' | 'very-complex';
  confidence?: number;
}

export interface NLPResult {
  success: boolean;
  entities: ExtractedEntities;
  classification: TaskClassification;
  structuredData: any;
  language: string;
  processingTime: number;
  tokensUsed?: number;
  errors?: string[];
}

/**
 * Text preprocessing utilities
 */
export class TextPreprocessor {
  /**
   * Clean and normalize input text
   */
  static preprocess(text: string): string {
    // Remove excessive whitespace
    let cleaned = text.replace(/\s+/g, ' ').trim();
    
    // Normalize quotes
    cleaned = cleaned.replace(/['']/g, "'").replace(/[""]/g, '"');
    
    // Fix common typos and standardize punctuation
    cleaned = cleaned.replace(/\s+([.,!?;:])/g, '$1');
    cleaned = cleaned.replace(/([.,!?;:])\s*/g, '$1 ');
    
    // Ensure sentences end with proper punctuation
    if (!/[.!?]$/.test(cleaned)) {
      cleaned += '.';
    }
    
    return cleaned.trim();
  }

  /**
   * Split text into sentences
   */
  static segmentSentences(text: string): string[] {
    // Advanced sentence splitting that handles abbreviations
    const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
    
    return sentences.map(s => s.trim()).filter(s => s.length > 0);
  }

  /**
   * Extract potential keywords
   */
  static extractKeywords(text: string): string[] {
    const stopWords = new Set([
      'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
      'of', 'with', 'by', 'from', 'as', 'is', 'was', 'are', 'were', 'be',
      'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
      'should', 'may', 'might', 'must', 'can', 'could', 'shall', 'should',
      'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we',
      'they', 'what', 'which', 'who', 'when', 'where', 'why', 'how'
    ]);

    const words = text.toLowerCase()
      .replace(/[^\w\s]/g, ' ')
      .split(/\s+/)
      .filter(word => word.length > 2 && !stopWords.has(word));

    // Count word frequency
    const wordFreq = new Map<string, number>();
    words.forEach(word => {
      wordFreq.set(word, (wordFreq.get(word) || 0) + 1);
    });

    // Return top keywords by frequency
    return Array.from(wordFreq.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10)
      .map(([word]) => word);
  }
}

/**
 * Main NLP Service for processing natural language input
 */
@injectable()
export class NLPService {
  private config: NLPConfig;
  private cache: Map<string, NLPResult> = new Map();

  constructor(
    private textParser: TextParserService,
    private entityExtractor: EntityExtractorService,
    private taskClassifier: TaskClassifierService,
    private structuredDataGenerator: StructuredDataGeneratorService,
    private openAIService: OpenAIService,
    private langChainService: LangChainService,
    private languageDetector: LanguageDetectorService,
    private translationService: TranslationService,
  ) {
    this.config = this.loadConfig();
  }

  /**
   * Load NLP configuration from environment
   */
  private loadConfig(): NLPConfig {
    return NLPConfigSchema.parse({
      enableCaching: process.env.NLP_ENABLE_CACHING !== 'false',
      cacheMaxAge: parseInt(process.env.NLP_CACHE_MAX_AGE || '3600000'),
      maxTokensPerRequest: parseInt(process.env.NLP_MAX_TOKENS || '2000'),
      defaultLanguage: process.env.NLP_DEFAULT_LANGUAGE || 'en',
      supportedLanguages: process.env.NLP_SUPPORTED_LANGUAGES?.split(',') || ['en', 'es', 'fr', 'de'],
      confidenceThreshold: parseFloat(process.env.NLP_CONFIDENCE_THRESHOLD || '0.7'),
    });
  }

  /**
   * Process natural language input through the full NLP pipeline
   */
  async processText(input: NLPInput): Promise<NLPResult> {
    const startTime = Date.now();
    const errors: string[] = [];

    try {
      // Check cache first
      const cacheKey = this.getCacheKey(input);
      if (this.config.enableCaching && this.cache.has(cacheKey)) {
        const cached = this.cache.get(cacheKey)!;
        if (Date.now() - cached.processingTime < this.config.cacheMaxAge) {
          return { ...cached, processingTime: Date.now() - startTime };
        }
      }

      // Preprocess text
      const preprocessedText = TextPreprocessor.preprocess(input.text);
      const keywords = TextPreprocessor.extractKeywords(preprocessedText);

      // Detect language if not provided using the dedicated service
      const languageDetection = input.language 
        ? { language: input.language, confidence: 1.0 }
        : await this.languageDetector.detectLanguage(preprocessedText);
      
      const language = languageDetection.language;
      
      // Apply locale-specific rules if not English
      let processedText = preprocessedText;
      let extractedLocaleTerms = new Map<string, string>();
      
      if (language !== 'en') {
        const localeResult = this.translationService.applyLocaleRules(preprocessedText, language);
        processedText = localeResult.normalizedText;
        extractedLocaleTerms = localeResult.extractedTerms;
      }

      // Parse text structure
      const parsedText = await this.textParser.parse({
        text: processedText,
        language,
        keywords,
      });

      // Extract entities
      const entities = await this.entityExtractor.extract({
        text: processedText,
        parsedData: parsedText,
        language,
        context: { ...input.context, localeTerms: Object.fromEntries(extractedLocaleTerms) },
      });

      // Classify task
      const classification = await this.taskClassifier.classify({
        text: processedText,
        entities,
        language,
        keywords,
      });

      // Generate structured data
      const structuredData = await this.structuredDataGenerator.generate({
        text: processedText,
        entities,
        classification,
        context: { ...input.context, localeTerms: Object.fromEntries(extractedLocaleTerms) },
        projectId: input.projectId,
      });

      // Calculate overall confidence
      const overallConfidence = this.calculateOverallConfidence(
        entities.confidence || 0,
        classification.confidence || 0
      );

      const result: NLPResult = {
        success: overallConfidence >= this.config.confidenceThreshold,
        entities,
        classification,
        structuredData,
        language,
        processingTime: Date.now() - startTime,
        tokensUsed: parsedText.tokensUsed || 0,
        errors: errors.length > 0 ? errors : undefined,
      };

      // Cache successful results
      if (result.success && this.config.enableCaching) {
        this.cache.set(cacheKey, result);
      }

      return result;

    } catch (error) {
      console.error('NLP processing error:', error);
      errors.push(error instanceof Error ? error.message : 'Unknown error');

      return {
        success: false,
        entities: {},
        classification: {},
        structuredData: null,
        language: input.language || this.config.defaultLanguage,
        processingTime: Date.now() - startTime,
        errors,
      };
    }
  }


  /**
   * Calculate overall confidence score
   */
  private calculateOverallConfidence(entityConfidence: number, classificationConfidence: number): number {
    // Weighted average with more weight on entity extraction
    return (entityConfidence * 0.6 + classificationConfidence * 0.4);
  }

  /**
   * Generate cache key for input
   */
  private getCacheKey(input: NLPInput): string {
    const key = `${input.text.substring(0, 100)}_${input.language || 'auto'}_${input.projectId || 'none'}`;
    return Buffer.from(key).toString('base64');
  }

  /**
   * Clear cache (useful for testing or memory management)
   */
  clearCache(): void {
    this.cache.clear();
  }

  /**
   * Get cache statistics
   */
  getCacheStats(): { size: number; maxAge: number } {
    return {
      size: this.cache.size,
      maxAge: this.config.cacheMaxAge,
    };
  }

  /**
   * Process text with translation to English if needed
   */
  async processTextWithTranslation(input: NLPInput): Promise<NLPResult> {
    // Detect language
    const languageDetection = await this.languageDetector.detectLanguage(input.text);
    
    // If not English and high confidence, translate to English first
    if (languageDetection.language !== 'en' && languageDetection.confidence > 0.7) {
      const translation = await this.translationService.translate({
        text: input.text,
        sourceLanguage: languageDetection.language,
        targetLanguage: 'en',
        preserveFormatting: true,
      });
      
      // Process the translated text
      const result = await this.processText({
        ...input,
        text: translation.translatedText,
        language: 'en',
        context: {
          ...input.context,
          originalLanguage: languageDetection.language,
          originalText: input.text,
        },
      });
      
      // Add translation metadata to result
      return {
        ...result,
        language: languageDetection.language,
        structuredData: {
          ...result.structuredData,
          translatedFrom: languageDetection.language,
          originalText: input.text,
        },
      };
    }
    
    // Process directly if already in English or low confidence
    return this.processText(input);
  }

  /**
   * Get supported languages
   */
  getSupportedLanguages(): string[] {
    return this.languageDetector.getSupportedLanguages().map(lang => lang.code);
  }

  /**
   * Check if a language is supported
   */
  isLanguageSupported(languageCode: string): boolean {
    return this.languageDetector.isLanguageSupported(languageCode);
  }
}

/**
 * NLP-specific error classes
 */
export class NLPError extends Error {
  constructor(message: string, public code: string, public details?: any) {
    super(message);
    this.name = 'NLPError';
  }
}

export class LanguageNotSupportedError extends NLPError {
  constructor(language: string) {
    super(`Language '${language}' is not supported`, 'LANGUAGE_NOT_SUPPORTED', { language });
  }
}

export class ConfidenceTooLowError extends NLPError {
  constructor(confidence: number, threshold: number) {
    super(
      `Confidence score ${confidence} is below threshold ${threshold}`,
      'CONFIDENCE_TOO_LOW',
      { confidence, threshold }
    );
  }
}

export class TokenLimitExceededError extends NLPError {
  constructor(tokens: number, limit: number) {
    super(
      `Token count ${tokens} exceeds limit ${limit}`,
      'TOKEN_LIMIT_EXCEEDED',
      { tokens, limit }
    );
  }
}