import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import OpenAI from 'openai';
import * as fs from 'fs';
import * as path from 'path';
import { nanoid } from 'nanoid';
import { VoiceFeedback } from '../../database/entities';
import { SaveVoiceFeedbackDto } from './dto/save-voice-feedback.dto';

export interface TranscriptionResult {
  text: string;
  language: string;
  duration?: number;
  confidence?: number;
  retryAttempt?: number;
}

export interface ActivityExtractionResult {
  type: string; // 'feeding', 'sleep', 'diaper', 'medicine', 'milestone', 'activity'
  timestamp?: Date;
  details: Record<string, any>;
  confidence: number;
  needsClarification?: boolean;
  clarificationPrompt?: string;
  alternatives?: ActivityExtractionResult[];
}

@Injectable()
export class VoiceService {
  private openai: OpenAI;
  private chatOpenAI: OpenAI;
  private logger = new Logger('VoiceService');

  // Supported languages for MVP
  private readonly SUPPORTED_LANGUAGES = ['en', 'es', 'fr', 'pt', 'zh'];

  // Confidence and retry configuration
  private readonly CONFIDENCE_THRESHOLD = 0.6; // Minimum acceptable confidence
  private readonly MAX_RETRIES = 3;
  private readonly RETRY_BASE_DELAY = 1000; // 1 second base delay

  constructor(
    private configService: ConfigService,
    @InjectRepository(VoiceFeedback)
    private readonly voiceFeedbackRepository: Repository<VoiceFeedback>,
  ) {
    // Check if Azure OpenAI is enabled
    const azureEnabled = this.configService.get<boolean>(
      'AZURE_OPENAI_ENABLED',
    );

    if (azureEnabled) {
      // Use Azure OpenAI for both Whisper and Chat
      const whisperEndpoint = this.configService.get<string>(
        'AZURE_OPENAI_WHISPER_ENDPOINT',
      );
      const whisperKey = this.configService.get<string>(
        'AZURE_OPENAI_WHISPER_API_KEY',
      );
      const chatEndpoint = this.configService.get<string>(
        'AZURE_OPENAI_CHAT_ENDPOINT',
      );
      const chatKey = this.configService.get<string>(
        'AZURE_OPENAI_CHAT_API_KEY',
      );

      if (whisperEndpoint && whisperKey) {
        this.openai = new OpenAI({
          apiKey: whisperKey,
          baseURL: `${whisperEndpoint}/openai/deployments/${this.configService.get<string>('AZURE_OPENAI_WHISPER_DEPLOYMENT')}`,
          defaultQuery: {
            'api-version': this.configService.get<string>(
              'AZURE_OPENAI_WHISPER_API_VERSION',
            ),
          },
          defaultHeaders: { 'api-key': whisperKey },
        });
        this.logger.log(
          'Azure OpenAI Whisper configured for voice transcription',
        );
      } else {
        this.logger.warn(
          'Azure OpenAI Whisper not fully configured. Voice transcription will be disabled.',
        );
      }

      if (chatEndpoint && chatKey) {
        this.chatOpenAI = new OpenAI({
          apiKey: chatKey,
          baseURL: `${chatEndpoint}/openai/deployments/${this.configService.get<string>('AZURE_OPENAI_CHAT_DEPLOYMENT')}`,
          defaultQuery: {
            'api-version': this.configService.get<string>(
              'AZURE_OPENAI_CHAT_API_VERSION',
            ),
          },
          defaultHeaders: { 'api-key': chatKey },
        });
        this.logger.log('Azure OpenAI Chat configured for activity extraction');
      } else {
        this.logger.warn(
          'Azure OpenAI Chat not configured. Using Whisper client for chat.',
        );
        this.chatOpenAI = this.openai;
      }
    } else {
      // Use standard OpenAI
      const apiKey = this.configService.get<string>('OPENAI_API_KEY');

      if (!apiKey || apiKey === 'sk-your-openai-api-key-here') {
        this.logger.warn(
          'OPENAI_API_KEY not configured. Voice features will be disabled.',
        );
      } else {
        this.openai = new OpenAI({
          apiKey,
        });
        this.chatOpenAI = this.openai;
        this.logger.log('OpenAI configured for voice transcription');
      }
    }
  }

  /**
   * Delay helper for exponential backoff
   */
  private async delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
   * Calculate exponential backoff delay
   */
  private calculateBackoffDelay(attempt: number): number {
    return this.RETRY_BASE_DELAY * Math.pow(2, attempt - 1);
  }

  /**
   * Transcribe audio with retry logic and exponential backoff
   */
  async transcribeAudioWithRetry(
    audioBuffer: Buffer,
    language?: string,
    maxRetries: number = this.MAX_RETRIES,
  ): Promise<TranscriptionResult> {
    let lastError: Error;
    let retryAttempt = 0;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        this.logger.log(
          `Transcription attempt ${attempt}/${maxRetries}`,
        );

        const result = await this.transcribeAudio(audioBuffer, language);
        result.retryAttempt = attempt;

        // Success - return result
        return result;
      } catch (error) {
        lastError = error;
        retryAttempt = attempt;

        this.logger.warn(
          `Transcription attempt ${attempt} failed: ${error.message}`,
        );

        // If not the last attempt, wait before retrying
        if (attempt < maxRetries) {
          const delay = this.calculateBackoffDelay(attempt);
          this.logger.log(`Retrying in ${delay}ms...`);
          await this.delay(delay);
        }
      }
    }

    // All retries exhausted
    this.logger.error(
      `All ${maxRetries} transcription attempts failed`,
      lastError.stack,
    );
    throw new BadRequestException(
      `Failed to transcribe audio after ${retryAttempt} attempts`,
    );
  }

  /**
   * Transcribe audio file to text using Whisper API
   */
  async transcribeAudio(
    audioBuffer: Buffer,
    language?: string,
  ): Promise<TranscriptionResult> {
    if (!this.openai) {
      throw new BadRequestException('Voice service not configured');
    }

    try {
      // Whisper API requires a file, so we need to create a temporary file
      const tempDir = path.join(process.cwd(), 'temp');
      if (!fs.existsSync(tempDir)) {
        fs.mkdirSync(tempDir, { recursive: true });
      }

      const tempFilePath = path.join(tempDir, `audio-${Date.now()}.wav`);
      fs.writeFileSync(tempFilePath, audioBuffer);

      const startTime = Date.now();

      const transcription = await this.openai.audio.transcriptions.create({
        file: fs.createReadStream(tempFilePath),
        model: 'whisper-1',
        language:
          language && this.SUPPORTED_LANGUAGES.includes(language)
            ? language
            : undefined, // Auto-detect if not specified
        response_format: 'verbose_json',
      });

      const duration = Date.now() - startTime;

      // Clean up temp file
      fs.unlinkSync(tempFilePath);

      this.logger.log(
        `Audio transcribed in ${duration}ms: "${transcription.text}" (${transcription.language})`,
      );

      return {
        text: transcription.text,
        language: transcription.language,
        duration,
      };
    } catch (error) {
      this.logger.error(`Transcription failed: ${error.message}`, error.stack);
      throw new BadRequestException('Failed to transcribe audio');
    }
  }

  /**
   * Extract activity information from transcribed text using GPT
   * Now supports multi-child detection and context
   */
  async extractActivityFromText(
    text: string,
    language: string,
    childName?: string,
    availableChildren?: Array<{ id: string; name: string }>,
  ): Promise<ActivityExtractionResult & { detectedChildName?: string; childId?: string }> {
    if (!this.chatOpenAI) {
      throw new BadRequestException('Chat service not configured');
    }

    this.logger.log(`[Activity Extraction] Starting extraction for: "${text}"`);
    this.logger.log(
      `[Activity Extraction] Language: ${language}, Child: ${childName || 'none'}`,
    );
    this.logger.log(
      `[Activity Extraction] Available children: ${availableChildren?.map(c => c.name).join(', ') || 'none'}`,
    );

    // Apply common mishear corrections before extraction
    const correctedText = this.applyMishearCorrections(text, language);

    try {
      // Build child context for multi-child families
      let childContext = '';
      if (availableChildren && availableChildren.length > 0) {
        const childNames = availableChildren.map(c => c.name).join(', ');
        childContext = `\n\n**Available Children in Family:** ${childNames}
- If the user mentions a specific child name, extract it and return in "childName" field
- Match child names case-insensitively and handle variations (e.g., "Emma", "emma", "Emmy")
- If multiple children exist but no name is mentioned, set "needsClarification" to true`;
      }

      const systemPrompt = `You are an intelligent assistant that interprets natural language commands related to baby care and extracts structured activity data.

Extract activity details from the user's text and respond ONLY with valid JSON (no markdown, no explanations).
${childContext}

**Supported Activity Types:**

1. **feeding** - Any mention of eating, drinking, nursing, bottle, breastfeeding, formula, solids, meals
   - Extract: amount (ml/oz), method (bottle/breast/solids), duration (minutes), side (left/right/both for breastfeeding), notes

2. **sleep** - Any mention of sleeping, napping, bedtime, waking up, dozed off
   - Extract: start_time, end_time, duration (minutes), quality (peaceful/restless/fussy), location (crib/bassinet/arms), notes

3. **diaper** - Any mention of diaper change, wet, dirty, bowel movement, pee, poop
   - Extract: type (wet/dirty/both), color, consistency, rash (true/false), notes

4. **medicine** - Any mention of medication, vitamin, supplement, drops, dose
   - Extract: name, dosage, unit, notes

5. **activity** - Any mention of play, exercise, walk, music, reading, tummy time, outdoor activities
   - Extract: activityType (play/walk/exercise/music/reading/tummy_time/outdoor/other), duration (minutes), description, notes

6. **milestone** - Any mention of first time events, developmental progress, achievements, new skills
   - Extract: description, category (motor/social/cognitive/language), notes

**Response Format:**
{
  "type": "feeding|sleep|diaper|medicine|activity|milestone|unknown",
  "timestamp": "ISO 8601 datetime if mentioned (e.g., '3pm', '30 minutes ago'), otherwise use current time",
  "childName": "extracted child name if mentioned, or null",
  "details": {
    // For feeding:
    "feedingType": "bottle|breast|solids",
    "amount": number or null,
    "unit": "ml|oz" or null,
    "side": "left|right|both" or null (for breastfeeding only),
    "duration": number (minutes) or null,
    "notes": string or null

    // For sleep:
    "quality": "peaceful|restless|fussy",
    "location": "crib|bassinet|arms|bed|stroller|car seat",
    "duration": number (minutes) or null,
    "notes": string or null

    // For diaper:
    "diaperType": "wet|dirty|both",
    "color": string or null,
    "consistency": string or null,
    "rash": boolean or null,
    "notes": string or null

    // For medicine:
    "medicineName": string,
    "dosage": number or string,
    "unit": string or null,
    "route": "oral|topical|injection|other" or null,
    "reason": string or null,
    "notes": string or null

    // For activity:
    "activityType": "play|walk|exercise|music|reading|tummy_time|outdoor|other",
    "duration": number (minutes) or null,
    "description": string or null,
    "notes": string or null

    // For milestone:
    "description": string,
    "category": "motor|social|cognitive|language" or null,
    "notes": string or null
  },
  "confidence": 0.0-1.0,
  "action": "create_activity|unknown"
}

**Important:**
- Be smart about interpreting casual language (e.g., "fed him" = feeding activity)
- Infer reasonable defaults (e.g., if amount is mentioned without unit and sounds like bottles, assume "ml")
- Use "unknown" type only if the text is clearly NOT about baby care
- Set confidence based on how clearly the activity is described
- Assume current time for timestamp if no time is mentioned
- Be flexible with language variations and typos

If the text doesn't describe a trackable baby care activity:
{"type": "unknown", "details": {}, "confidence": 0, "action": "unknown"}`;

      const userPrompt = childName
        ? `Child name: ${childName}\nUser said: "${correctedText}"`
        : `User said: "${correctedText}"`;

      this.logger.log(
        `[Activity Extraction] Calling GPT-4o-mini with user prompt: ${userPrompt}`,
      );

      const startTime = Date.now();
      const completion = await this.chatOpenAI.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: userPrompt },
        ],
        response_format: { type: 'json_object' },
      });
      const duration = Date.now() - startTime;

      this.logger.log(
        `[Activity Extraction] GPT response received in ${duration}ms`,
      );
      this.logger.log(
        `[Activity Extraction] Raw GPT response: ${completion.choices[0].message.content}`,
      );

      const result = JSON.parse(completion.choices[0].message.content);

      this.logger.log(
        `[Activity Extraction] Extracted activity: ${result.type} (confidence: ${result.confidence})`,
      );
      this.logger.log(
        `[Activity Extraction] Child name: ${result.childName || 'none'}`,
      );
      this.logger.log(
        `[Activity Extraction] Details: ${JSON.stringify(result.details || {})}`,
      );

      const extractedActivity: ActivityExtractionResult & { detectedChildName?: string; childId?: string } = {
        type: result.type,
        timestamp: result.timestamp ? new Date(result.timestamp) : null,
        details: result.details || {},
        confidence: result.confidence || 0,
        detectedChildName: result.childName || undefined,
      };

      // Match detected child name to child ID
      if (extractedActivity.detectedChildName && availableChildren) {
        const matchedChild = availableChildren.find(
          child => child.name.toLowerCase() === extractedActivity.detectedChildName!.toLowerCase()
        );
        if (matchedChild) {
          extractedActivity.childId = matchedChild.id;
          this.logger.log(
            `[Activity Extraction] Matched child "${extractedActivity.detectedChildName}" to ID: ${matchedChild.id}`,
          );
        } else {
          this.logger.warn(
            `[Activity Extraction] Could not match child name "${extractedActivity.detectedChildName}" to any available child`,
          );
        }
      }

      // Check if clarification needed for multi-child families
      if (availableChildren && availableChildren.length > 1 && !extractedActivity.detectedChildName) {
        extractedActivity.needsClarification = true;
        extractedActivity.clarificationPrompt = `Which child is this for? Available: ${availableChildren.map(c => c.name).join(', ')}`;
        this.logger.warn(
          `Multi-child family but no child name detected - clarification needed`,
        );
      }

      // Check if confidence is below threshold
      if (extractedActivity.confidence < this.CONFIDENCE_THRESHOLD) {
        extractedActivity.needsClarification = true;
        extractedActivity.clarificationPrompt = this.generateClarificationPrompt(
          text,
          extractedActivity,
        );

        this.logger.warn(
          `Low confidence (${extractedActivity.confidence}) - clarification needed`,
        );
      }

      return extractedActivity;
    } catch (error) {
      this.logger.error(
        `[Activity Extraction] Failed: ${error.message}`,
        error.stack,
      );
      throw new BadRequestException('Failed to extract activity from text');
    }
  }

  /**
   * Process voice input: transcribe + extract activity
   * Now supports multi-child context
   */
  async processVoiceInput(
    audioBuffer: Buffer,
    language?: string,
    childName?: string,
    availableChildren?: Array<{ id: string; name: string }>,
  ): Promise<{
    transcription: TranscriptionResult;
    activity: ActivityExtractionResult & { detectedChildName?: string; childId?: string };
  }> {
    // Step 1: Transcribe audio
    const transcription = await this.transcribeAudio(audioBuffer, language);

    // Step 2: Extract activity from transcription with multi-child support
    const activity = await this.extractActivityFromText(
      transcription.text,
      transcription.language,
      childName,
      availableChildren,
    );

    return {
      transcription,
      activity,
    };
  }

  /**
   * Generate clarification question for ambiguous input
   */
  async generateClarificationQuestion(
    text: string,
    activityType: string,
    language: string,
  ): Promise<string> {
    if (!this.chatOpenAI) {
      throw new BadRequestException('Chat service not configured');
    }

    try {
      const systemPrompt = `You are a helpful assistant for tracking baby activities.
The user provided input about a "${activityType}" activity, but some information is missing or unclear.

Generate a brief, friendly clarification question in ${language} to help complete the activity log.
Respond ONLY with the question text, no formatting.`;

      const completion = await this.chatOpenAI.chat.completions.create({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: `User said: "${text}"` },
        ],
        temperature: 0.7,
        max_tokens: 100,
      });

      return completion.choices[0].message.content.trim();
    } catch (error) {
      this.logger.error(
        `Clarification generation failed: ${error.message}`,
        error.stack,
      );
      // Fallback to generic question
      return 'Could you provide more details about this activity?';
    }
  }

  /**
   * Generate clarification prompt for low confidence results
   */
  private generateClarificationPrompt(
    originalText: string,
    activity: ActivityExtractionResult,
  ): string {
    const activityTypeMap = {
      feeding: 'feeding',
      sleep: 'sleep/nap',
      diaper: 'diaper change',
      medicine: 'medicine',
      milestone: 'milestone',
      activity: 'activity',
      unknown: 'activity',
    };

    const activityName = activityTypeMap[activity.type] || 'activity';

    return `I understood "${originalText}" as a ${activityName}, but I'm not completely sure. Is this correct? If not, please try rephrasing or provide more details.`;
  }

  /**
   * Apply common mishear corrections to transcribed text
   */
  private applyMishearCorrections(text: string, language: string): string {
    let corrected = text;

    // Common baby-related mishears (English)
    const englishCorrections = {
      'feet': 'feed',
      'feeding time': 'feeding time',
      'sleep time': 'sleep time',
      'nap time': 'nap time',
      'dipper': 'diaper',
      'diper': 'diaper',
      'medicin': 'medicine',
      'medisin': 'medicine',
      'milk time': 'feeding time',
      'bed time': 'sleep time',
      'wake up': 'woke up',
      'bottle feed': 'bottle feeding',
      'breast feed': 'breastfeeding',
    };

    // Spanish corrections
    const spanishCorrections = {
      'comida': 'alimentación',
      'pañal': 'cambio de pañal',
      'medicina': 'medicamento',
    };

    // French corrections
    const frenchCorrections = {
      'couche': 'changement de couche',
      'repas': 'alimentation',
    };

    const corrections = language === 'es' ? spanishCorrections :
                       language === 'fr' ? frenchCorrections :
                       englishCorrections;

    // Apply corrections (case-insensitive)
    Object.entries(corrections).forEach(([wrong, correct]) => {
      const regex = new RegExp(`\\b${wrong}\\b`, 'gi');
      corrected = corrected.replace(regex, correct);
    });

    if (corrected !== text) {
      this.logger.log(
        `Applied mishear correction: "${text}" → "${corrected}"`,
      );
    }

    return corrected;
  }

  /**
   * Save user feedback on voice command accuracy
   */
  async saveFeedback(
    userId: string,
    feedbackDto: SaveVoiceFeedbackDto,
  ): Promise<VoiceFeedback> {
    try {
      const feedback = this.voiceFeedbackRepository.create({
        id: `vfb_${nanoid(16)}`,
        userId,
        childId: feedbackDto.childId,
        activityId: feedbackDto.activityId,
        transcript: feedbackDto.transcript,
        language: feedbackDto.language,
        extractedType: feedbackDto.extractedType,
        extractedData: feedbackDto.extractedData,
        confidence: feedbackDto.confidence,
        action: feedbackDto.action,
        finalType: feedbackDto.finalType,
        finalData: feedbackDto.finalData,
        userNotes: feedbackDto.userNotes,
      });

      const saved = await this.voiceFeedbackRepository.save(feedback);

      this.logger.log(
        `[Voice Feedback] User ${userId} ${feedbackDto.action} voice command for type: ${feedbackDto.extractedType}`,
      );

      return saved;
    } catch (error) {
      this.logger.error(
        `[Voice Feedback] Failed to save: ${error.message}`,
        error.stack,
      );
      throw new BadRequestException('Failed to save voice feedback');
    }
  }
}
