import { prisma } from '../index';
import { createError } from '../middlewares/errorHandler';
import logger from '../utils/logger';
import OpenAI from 'openai';

export class OpenAIService {
  private static instance: OpenAIService;
  private apiKey: string | null = null;
  private assistantId: string | null = null;
  private lastKeyFetch: number = 0;
  private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes

  private constructor() {
    logger.info('🔧 OpenAIService singleton created', {
      cacheDuration: `${this.CACHE_DURATION}ms`
    });
  }

  public static getInstance(): OpenAIService {
    if (!OpenAIService.instance) {
      logger.info('🏭 Creating OpenAIService singleton instance');
      OpenAIService.instance = new OpenAIService();
    }
    return OpenAIService.instance;
  }

  /**
   * Get the current API key and assistant ID from database
   */
  private async getConfig(): Promise<{ apiKey: string; assistantId: string } | null> {
    const now = Date.now();
    
    logger.info('🔑 Retrieving OpenAI configuration', {
      hasCachedKey: !!this.apiKey,
      hasCachedAssistantId: !!this.assistantId,
      cacheAge: this.apiKey ? `${now - this.lastKeyFetch}ms` : 'N/A',
      cacheValid: this.apiKey && (now - this.lastKeyFetch) < this.CACHE_DURATION
    });
    
    // Use cached config if still valid. The assistant ID is optional (e.g. translation
    // only needs the API key), so it's not a condition for cache validity.
    if (this.apiKey && (now - this.lastKeyFetch) < this.CACHE_DURATION) {
      logger.info('✅ Using cached OpenAI configuration', {
        keyLength: this.apiKey.length,
        hasAssistantId: !!this.assistantId,
        cacheAge: `${now - this.lastKeyFetch}ms`
      });
      return { apiKey: this.apiKey, assistantId: this.assistantId || '' };
    }

    // Get configuration from database
    try {
      logger.info('🗄️ Querying database for OpenAI configuration');
      const config = await (prisma as any).openAIConfig.findFirst({
        where: { isActive: true },
        orderBy: { createdAt: 'desc' }
      });

      if (config?.apiKey) {
        logger.info('✅ OpenAI configuration found in database', {
          configId: config.id,
          hasApiKey: !!config.apiKey,
          hasAssistantId: !!config.assistantId
        });
        this.apiKey = config.apiKey;
        this.assistantId = config.assistantId || '';
        this.lastKeyFetch = now;
        return { apiKey: config.apiKey, assistantId: config.assistantId || '' };
      }

      logger.warn('⚠️ No OpenAI configuration found in database');
      return null;
    } catch (error) {
      logger.error('❌ Failed to retrieve OpenAI configuration from database', { 
        error: error instanceof Error ? error.message : String(error)
      });
      return null;
    }
  }

  /**
   * Invalidate the cached configuration
   */
  public invalidateCache(): void {
    logger.info('🗑️ Invalidating OpenAI configuration cache', {
      hadCachedKey: !!this.apiKey,
      hadCachedAssistantId: !!this.assistantId,
      cacheAge: this.apiKey ? `${Date.now() - this.lastKeyFetch}ms` : 'N/A'
    });
    
    this.apiKey = null;
    this.assistantId = null;
    this.lastKeyFetch = 0;
  }

  /**
   * Update OpenAI configuration. Assistant ID is optional — some features (e.g.
   * summary translation) only need the API key; others (structured feedback) need
   * the assistant too and validate that separately when they're actually used.
   */
  async updateConfig(apiKey: string, assistantId: string = ''): Promise<void> {
    logger.info('🔧 Updating OpenAI configuration', {
      hasApiKey: !!apiKey,
      hasAssistantId: !!assistantId,
      keyLength: apiKey.length,
      assistantIdLength: assistantId.length
    });

    try {
      // Deactivate all existing configurations
      await (prisma as any).openAIConfig.updateMany({
        where: { isActive: true },
        data: { isActive: false }
      });

      // Create new configuration
      await (prisma as any).openAIConfig.create({
        data: {
          apiKey,
          assistantId,
          isActive: true
        }
      });

      // Invalidate cache to force reload
      this.invalidateCache();

      logger.info('✅ OpenAI configuration updated successfully');
    } catch (error) {
      logger.error('❌ Failed to update OpenAI configuration', {
        error: error instanceof Error ? error.message : String(error)
      });
      throw createError('Failed to update OpenAI configuration', 500);
    }
  }

  /**
   * Update just the API key, preserving whatever assistant ID (if any) is
   * already saved — so the two fields can be saved independently.
   */
  async updateApiKey(apiKey: string): Promise<void> {
    const current = await this.getConfig();
    await this.updateConfig(apiKey, current?.assistantId || '');
  }

  /**
   * Update just the assistant ID, preserving the already-saved API key.
   * Requires an API key to already be configured — an assistant on its own
   * is meaningless without it.
   */
  async updateAssistantId(assistantId: string): Promise<void> {
    const current = await this.getConfig();
    if (!current?.apiKey) {
      throw createError('Configure the API key before setting an Assistant ID', 400);
    }
    await this.updateConfig(current.apiKey, assistantId);
  }

  /**
   * Test OpenAI connection
   */
  async testConnection(): Promise<boolean> {
    logger.info('🧪 Testing OpenAI connection');

    try {
      const config = await this.getConfig();
      if (!config) {
        logger.warn('⚠️ No OpenAI configuration found for testing');
        return false;
      }

      const openai = new OpenAI({
        apiKey: config.apiKey,
      });

      // Test by listing models (simple API call)
      const models = await openai.models.list();
      
      logger.info('✅ OpenAI connection test successful', {
        modelsCount: models.data.length,
        hasModels: models.data.length > 0
      });

      return true;
    } catch (error: any) {
      logger.error('❌ OpenAI connection test failed', { 
        error: error.message,
        status: error.status,
        type: error.type
      });
      return false;
    }
  }

  /**
   * Generate conversation feedback using OpenAI Assistant API
   */
  async generateFeedback(transcript: string, topic: string, feedback: string, level: string): Promise<{
    summary: string;
    evaluation: string;
    feedback: string;
    transcript: string;
  }> {
    const startTime = Date.now();
    
    logger.info('📝 Generating conversation feedback with OpenAI', {
      transcriptLength: transcript.length,
      topicLength: topic?.length || 0,
      feedbackLength: feedback?.length || 0,
      level,
      requestTime: new Date().toISOString()
    });

    try {
      const config = await this.getConfig();
      if (!config || !config.apiKey) {
        throw createError('OpenAI not configured', 400);
      }

      const openai = new OpenAI({
        apiKey: config.apiKey,
      });

      // Use assistant ID from database configuration
      if (!config.assistantId || config.assistantId.trim() === '') {
        throw createError('OpenAI Assistant ID not configured. Please configure it in the admin panel.', 400);
      }
      const ASSISTANT_ID = config.assistantId;

      logger.info('🌐 Making API request to OpenAI Assistant', {
        assistantId: ASSISTANT_ID,
        transcriptLength: transcript.length
      });

      // Create a thread
      const thread = await openai.beta.threads.create();

      // Add message to thread with bot topic, feedback instructions, level and transcript
      await openai.beta.threads.messages.create(thread.id, {
        role: 'user',
        content: `Bot Topic: ${topic}
        Feedback Instructions: ${feedback}
        Bot Level: ${level}
        Transcript: ${transcript}
`});

      // Run the assistant
      const run = await openai.beta.threads.runs.create(thread.id, {
        assistant_id: ASSISTANT_ID
      });

      // Wait for completion
      let runStatus = await openai.beta.threads.runs.retrieve(run.id, { thread_id: thread.id });
      while (runStatus.status === 'queued' || runStatus.status === 'in_progress') {
        await new Promise(resolve => setTimeout(resolve, 1000));
        runStatus = await openai.beta.threads.runs.retrieve(run.id, { thread_id: thread.id });
      }

      if (runStatus.status !== 'completed') {
        throw createError(`Assistant run failed with status: ${runStatus.status}`, 500);
      }

      // Get the response
      const messages = await openai.beta.threads.messages.list(thread.id);
      // Take the last assistant message
      const assistantMessage = messages.data.find(msg => msg.role === 'assistant') || messages.data[0];
      
      if (!assistantMessage) {
        throw createError('No response from assistant', 500);
      }

      // Concatenar todos los bloques de contenido de texto
      const responseText = (assistantMessage.content || [])
        .map((c: any) => (c?.type === 'text' ? (c.text?.value || '') : ''))
        .filter(Boolean)
        .join('\n\n')
        .trim();

      if (!responseText) {
        throw createError('Empty response from assistant', 500);
      }
      const responseTime = Date.now() - startTime;

      logger.info('📡 OpenAI Assistant response received', {
        responseLength: responseText.length,
        responseTime: `${responseTime}ms`,
        status: runStatus.status
      });

      // Parse the response into sections
      const sections = this.parseAssistantResponse(responseText);

      logger.info('✅ Conversation feedback generated successfully', {
        hasSummary: !!sections.summary,
        hasEvaluation: !!sections.evaluation,
        hasFeedback: !!sections.feedback,
        hasTranscript: !!sections.transcript
      });

      return sections;

    } catch (error: any) {
      const responseTime = Date.now() - startTime;
      logger.error('❌ Failed to generate conversation feedback', {
        error: error.message,
        responseTime: `${responseTime}ms`,
        transcriptLength: transcript.length
      });
      throw createError(`Failed to generate feedback: ${error.message}`, 500);
    }
  }

  /**
   * Parse assistant response into structured sections
   */
  private parseAssistantResponse(response: string): {
    summary: string;
    evaluation: string;
    feedback: string;
    transcript: string;
  } {
    // Intentar parsear en formato markdown con encabezados claros (### o **LABEL**)
    const out = { summary: '', evaluation: '', feedback: '', transcript: '' } as any
    const text = response.replace(/\r/g, '')

    // 1) Encabezados markdown ### SUMMARY / ### EVALUATION / ### FEEDBACK / ### TRANSCRIPT
    const blocks = text.split(/\n(?=#+\s)/) // dividir por encabezados markdown
    if (blocks.length > 1) {
      for (const b of blocks) {
        const header = (b.match(/^#+\s*(.+)/)?.[1] || '').toLowerCase()
        const body = b.replace(/^#+\s*.+\n?/, '').trim()
        if (header.includes('summary') || header.includes('resumen')) out.summary = (out.summary ? out.summary + '\n\n' : '') + body
        else if (header.includes('evaluation') || header.includes('evaluación')) out.evaluation = (out.evaluation ? out.evaluation + '\n\n' : '') + body
        else if (header.includes('feedback') || header.includes('retroalimentación')) out.feedback = (out.feedback ? out.feedback + '\n\n' : '') + body
        else if (header.includes('transcript') || header.includes('transcripción')) out.transcript = (out.transcript ? out.transcript + '\n\n' : '') + body
      }
    }

    // 2) Patrón de labels en línea: **SUMMARY**:, **EVALUATION**:, etc.
    const labelRegex = /\*\*\s*(summary|resumen|evaluation|evaluación|feedback|retroalimentación|transcript|transcripción)\s*\*\*\s*[:：]\s*/ig
    if (!out.summary && !out.evaluation && !out.feedback) {
      let last = 'feedback'
      const parts = text.split(labelRegex)
      for (let i = 0; i < parts.length; i++) {
        const seg = parts[i]
        if (!seg) continue
        const lower = seg.toLowerCase()
        if (['summary','resumen','evaluation','evaluación','feedback','retroalimentación','transcript','transcripción'].includes(lower)) {
          last = (lower.startsWith('resumen') || lower.startsWith('summary')) ? 'summary'
            : (lower.startsWith('evalu') ? 'evaluation'
            : (lower.startsWith('trans') ? 'transcript' : 'feedback'))
        } else {
          out[last] = (out[last] ? out[last] + '\n\n' : '') + seg.trim()
        }
      }
    }

    // 3) Fallback: si nada se detectó, todo el texto es feedback
    if (!out.summary && !out.evaluation && !out.feedback) out.feedback = text
    return out as { summary: string; evaluation: string; feedback: string; transcript: string }
  }

  /**
   * Translate text to the target language using a chat completion. Never throws:
   * on any failure (not configured, API error, ...) it logs a warning and returns
   * the original text unchanged, so callers can always use the result directly.
   */
  async translateText(text: string, target: string = 'Spanish'): Promise<string> {
    if (!text || !text.trim()) return text;

    try {
      const config = await this.getConfig();
      if (!config?.apiKey) {
        logger.warn('⚠️ Skipping translation: OpenAI API key not configured');
        return text;
      }

      const openai = new OpenAI({ apiKey: config.apiKey });
      const model = process.env.OPENAI_TRANSLATION_MODEL || 'gpt-4o-mini';

      const completion = await openai.chat.completions.create({
        model,
        temperature: 0,
        messages: [
          {
            role: 'system',
            content: `You translate text to ${target}. If the text is already in ${target}, return it unchanged. Reply with only the translated text — no quotes, no commentary.`
          },
          { role: 'user', content: text }
        ]
      });

      const translated = completion.choices[0]?.message?.content?.trim();
      return translated || text;
    } catch (error: any) {
      logger.warn('⚠️ Translation failed, using original text', { error: error.message, textLength: text.length });
      return text;
    }
  }

  /**
   * Translate the transcript summary embedded in the raw ElevenLabs analysis object
   * (used by the "Resumen de la transcripción" tab), plus the plain DB summary column
   * used as its last-resort fallback — so that tab is readable in Spanish regardless
   * of which fallback field ends up populated for a given conversation. Shared by the
   * 3 ingestion points (auto-ingest, ensureConversationFeedback, webhookEvaluation) to
   * avoid tripling this logic.
   */
  async translateSummaryFields(analysis: any, dbSummary: string): Promise<{ analysis: any; summary: string }> {
    let translatedAnalysis = analysis;
    let translatedSummary = dbSummary;

    const rawTranscriptSummary = analysis?.transcript_summary;
    if (typeof rawTranscriptSummary === 'string' && rawTranscriptSummary.trim()) {
      const translated = await this.translateText(rawTranscriptSummary, 'Spanish');
      translatedAnalysis = { ...analysis, transcript_summary: translated, transcript_summary_original: rawTranscriptSummary };
    }

    // 'Evaluation received' is our own placeholder (not real ElevenLabs content) — the
    // frontend matches it by exact string, so it must never be translated.
    if (dbSummary && dbSummary.trim() && dbSummary !== 'Evaluation received') {
      translatedSummary = await this.translateText(dbSummary, 'Spanish');
    }

    return { analysis: translatedAnalysis, summary: translatedSummary };
  }

  /**
   * Get current configuration status
   */
  async getCurrentConfig(): Promise<{
    hasApiKey: boolean;
    hasAssistantId: boolean;
    isValid: boolean;
    createdAt?: Date;
    maskedApiKey?: string;
    maskedAssistantId?: string;
  }> {
    try {
      const config = await (prisma as any).openAIConfig.findFirst({
        where: { isActive: true },
        orderBy: { createdAt: 'desc' }
      });

      if (!config) {
        return { hasApiKey: false, hasAssistantId: false, isValid: false };
      }

      const hasApiKey = !!(config.apiKey && config.apiKey.trim().length > 0);
      const hasAssistantId = !!(config.assistantId && config.assistantId.trim().length > 0);

      // Test connection with just the API key — the assistant is only required by
      // features that actually use it (e.g. structured feedback), not by this check.
      let isValid = false;
      if (hasApiKey) {
        isValid = await this.testConnection();
      }

      return {
        hasApiKey,
        hasAssistantId,
        isValid,
        createdAt: config.createdAt,
        maskedApiKey: hasApiKey ? `sk-...${config.apiKey.slice(-4)}` : undefined,
        maskedAssistantId: hasAssistantId ? `asst_...${config.assistantId.slice(-4)}` : undefined
      };
    } catch (error) {
      logger.error('❌ Failed to get current OpenAI configuration', { 
        error: error instanceof Error ? error.message : String(error)
      });
      return { hasApiKey: false, hasAssistantId: false, isValid: false };
    }
  }
}

// Export singleton instance
export const openaiService = OpenAIService.getInstance();
