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

export class ElevenLabsService {
  private static instance: ElevenLabsService;
  private apiKey: string | null = null;
  private lastKeyFetch: number = 0;
  private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
  private agentConfigCache = new Map<string, { data: any; fetchedAt: number }>();
  private readonly AGENT_CONFIG_CACHE_DURATION = 10 * 60 * 1000; // 10 minutes

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

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

  /**
   * Get the current API key from database only
   */
  private async getApiKey(): Promise<string | null> {
    const now = Date.now();
    
    logger.info('🔑 Retrieving ElevenLabs API key', {
      hasCachedKey: !!this.apiKey,
      cacheAge: this.apiKey ? `${now - this.lastKeyFetch}ms` : 'N/A',
      cacheValid: this.apiKey && (now - this.lastKeyFetch) < this.CACHE_DURATION
    });
    
    // Use cached key if still valid
    if (this.apiKey && (now - this.lastKeyFetch) < this.CACHE_DURATION) {
      logger.info('✅ Using cached API key', {
        keyLength: this.apiKey.length,
        cacheAge: `${now - this.lastKeyFetch}ms`
      });
      return this.apiKey;
    }

    // Get API key from database
    try {
      logger.info('🗄️ Querying database for API key configuration');
      const config = await prisma.elevenLabsConfig.findFirst({
        where: { isActive: true },
        orderBy: { createdAt: 'desc' }
      });
      if (config?.apiKey) {
        logger.info('✅ API key found in database', { configId: config.id });
        this.apiKey = config.apiKey;
        this.lastKeyFetch = now;
        return this.apiKey;
      }
      logger.warn('⚠️ No ElevenLabs API key configured in database');
      return null;
    } catch (error) {
      logger.error('❌ Failed to retrieve API key from database', { error: (error as any).message });
      return null;
    }
  }

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

  /**
   * Get signed URL for WebSocket connection
   */
  async getSignedUrl(agentId: string): Promise<string> {
    const startTime = Date.now();
    
    logger.info('🔗 Getting signed URL from ElevenLabs', {
      agentId,
      requestTime: new Date().toISOString()
    });

    // Check if we have a real API key
    let hasRealApiKey = false;
    try {
      const apiKey = await this.getApiKey();
      hasRealApiKey = !!(apiKey && apiKey.trim().length > 0);
    } catch (error: any) {
      logger.info('⚠️ No real API key configured', {
        error: error?.message
      });
    }

    const isProd = process.env.NODE_ENV === 'production';
    const isTestLikeAgent = /(^test-|\btest\b|mock|dev|sandbox)/i.test(agentId);

    // In production, never use mock; require a real API key
    if (isProd && !hasRealApiKey) {
      logger.error('❌ ElevenLabs API key not configured in database');
      throw createError('ElevenLabs API key not configured', 500);
    }

    // In non-production, allow mock if missing key or test-like agentId
    if (!isProd && (!hasRealApiKey || isTestLikeAgent)) {
      logger.info('🧪 Development mode - using mock signed URL', {
        agentId,
        isTestAgent: isTestLikeAgent,
        reason: !hasRealApiKey ? 'No real API key configured' : 'Test-like agentId'
      });

      // Return a mock WebSocket URL for development
      const mockSignedUrl = `wss://mock.elevenlabs.dev/conversation?agent_id=${agentId}&token=mock-token-${Date.now()}`;

      const totalTime = Date.now() - startTime;

      logger.info('✅ Mock signed URL generated for development', {
        agentId,
        mockUrl: mockSignedUrl.substring(0, 50) + '...',
        totalTime: `${totalTime}ms`
      });

      return mockSignedUrl;
    }

    try {
      const apiKey = await this.getApiKey();
      
      logger.info('🌐 Making API request to ElevenLabs', {
        agentId,
        endpoint: 'get-signed-url',
        hasApiKey: !!apiKey
      });
      
      const response = await fetch(
        `https://api.elevenlabs.io/v1/convai/conversation/get-signed-url?agent_id=${agentId}`,
        {
          headers: {
            'xi-api-key': apiKey,
          },
        }
      );

      const responseTime = Date.now() - startTime;
      
      logger.info('📡 ElevenLabs API response received', {
        agentId,
        status: response.status,
        statusText: response.statusText,
        responseTime: `${responseTime}ms`,
        hasResponse: !!response
      });

      if (!response.ok) {
        let errorMessage = `Failed to get signed URL: ${response.status} ${response.statusText}`;
        
        // Provide more specific error messages based on status code
        if (response.status === 404) {
          errorMessage = `El agente con ID "${agentId}" no existe. Por favor, verifica que el agentId esté configurado correctamente en la base de datos.`;
        } else if (response.status === 401) {
          errorMessage = `La API key no es válida o ha expirado. Por favor, configura una nueva API key en el panel de administración.`;
        } else if (response.status === 403) {
          errorMessage = `No tienes permisos para acceder a este agente. Verifica tu API key y los permisos de tu cuenta.`;
        } else if (response.status >= 500) {
          errorMessage = `Error interno del servidor. Intenta nuevamente en unos minutos.`;
        }
        
        logger.error('❌ Failed to get signed URL', {
          agentId,
          status: response.status,
          statusText: response.statusText,
          responseTime: `${responseTime}ms`,
          errorMessage
        });
        throw createError(errorMessage, response.status);
      }

      const data: any = await response.json();
      
      logger.info('📄 Parsing ElevenLabs response', {
        agentId,
        hasData: !!data,
        dataKeys: data ? Object.keys(data) : [],
        hasSignedUrl: !!data?.signed_url
      });
      
      if (!data.signed_url) {
        logger.error('❌ Invalid response from ElevenLabs API', {
          agentId,
          responseData: data,
          hasSignedUrl: !!data?.signed_url
        });
        throw createError('Invalid response from ElevenLabs API', 500);
      }

      const totalTime = Date.now() - startTime;
      
      logger.info('✅ Signed URL retrieved successfully', {
        agentId,
        signedUrl: data.signed_url.substring(0, 50) + '...',
        totalTime: `${totalTime}ms`
      });
      
      return data.signed_url;
    } catch (error) {
      const totalTime = Date.now() - startTime;
      
      logger.error('❌ Error getting signed URL', { 
        error: error.message,
        agentId,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
      throw error;
    }
  }

  /**
   * Get conversation token for ElevenLabs
   */
  async getConversationToken(agentId: string): Promise<string> {
    try {
      const apiKey = await this.getApiKey();
      
      if (!apiKey) {
        throw createError('ElevenLabs API key not configured. Please configure it in the admin panel.', 400);
      }

      const url = `https://api.elevenlabs.io/v1/convai/conversation/token?agent_id=${agentId}`;
      
      logger.info('🔗 Getting conversation token from ElevenLabs', {
        agentId,
        url: url.substring(0, 50) + '...'
      });

      const response = await fetch(url, {
        headers: {
          'xi-api-key': apiKey,
        },
      });

      if (!response.ok) {
        const errorText = await response.text();
        logger.error('❌ Failed to get conversation token', {
          status: response.status,
          statusText: response.statusText,
          error: errorText
        });
        throw new Error(`Failed to get conversation token: ${response.status} ${response.statusText}`);
      }

      const data: any = await response.json();
      const token = data.token;
      
      logger.info('✅ Conversation token obtained', {
        agentId,
        tokenLength: token ? token.length : 0
      });

      return token;
    } catch (error) {
      logger.error('❌ Error getting conversation token', {
        agentId,
        error: error instanceof Error ? error.message : 'Unknown error'
      });
      throw error;
    }
  }

  /**
   * Validate that an agent ID exists and is accessible
   */
  async validateAgentId(agentId: string): Promise<boolean> {
    const startTime = Date.now();
    
    logger.info('🔍 Validating agent ID with ElevenLabs', {
      agentId,
      requestTime: new Date().toISOString()
    });

    try {
      const apiKey = await this.getApiKey();
      
      logger.info('🌐 Making API request to ElevenLabs', {
        agentId,
        endpoint: 'agents/{id}',
        hasApiKey: !!apiKey
      });
      
      // Try to get agent info to validate it exists
      const response = await fetch(
        `https://api.elevenlabs.io/v1/convai/agents/${agentId}`,
        {
          headers: {
            'xi-api-key': apiKey,
          },
        }
      );

      const responseTime = Date.now() - startTime;
      
      logger.info('📡 ElevenLabs API response received', {
        agentId,
        status: response.status,
        statusText: response.statusText,
        responseTime: `${responseTime}ms`
      });

      if (response.status === 404) {
        logger.warn('⚠️ Agent ID not found (404)', {
          agentId,
          responseTime: `${responseTime}ms`
        });
        return false;
      }

      if (!response.ok) {
        logger.warn('⚠️ Failed to validate agent ID', {
          agentId,
          status: response.status,
          statusText: response.statusText,
          responseTime: `${responseTime}ms`
        });
        return false;
      }

      const totalTime = Date.now() - startTime;
      
      logger.info('✅ Agent ID validated successfully', { 
        agentId,
        totalTime: `${totalTime}ms`
      });
      
      return true;
    } catch (error) {
      const totalTime = Date.now() - startTime;
      
      logger.error('❌ Error validating agent ID', { 
        error: error.message,
        agentId,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
      return false;
    }
  }

  /**
   * Fetch (and cache) an agent's full config from ElevenLabs. Shared by
   * getEvaluationCriteriaNames() and getAgentLanguage() so both features reuse the
   * same request/cache instead of hitting the ElevenLabs API twice per agent.
   */
  private async getAgentConfig(agentId: string): Promise<any | null> {
    const cached = this.agentConfigCache.get(agentId);
    const now = Date.now();

    if (cached && (now - cached.fetchedAt) < this.AGENT_CONFIG_CACHE_DURATION) {
      return cached.data;
    }

    try {
      const apiKey = await this.getApiKey();
      if (!apiKey) {
        return cached?.data || null;
      }

      const response = await fetch(
        `https://api.elevenlabs.io/v1/convai/agents/${agentId}`,
        {
          headers: {
            'xi-api-key': apiKey,
          },
        }
      );

      if (!response.ok) {
        logger.warn('⚠️ Failed to fetch agent config', {
          agentId,
          status: response.status,
          statusText: response.statusText
        });
        return cached?.data || null;
      }

      const data: any = await response.json();
      this.agentConfigCache.set(agentId, { data, fetchedAt: now });
      return data;
    } catch (error: any) {
      logger.warn('⚠️ Error fetching agent config', {
        agentId,
        error: error.message
      });
      return cached?.data || null;
    }
  }

  /**
   * Get the evaluation criteria configured for an agent, mapped from ElevenLabs'
   * auto-generated criterion id to the human-readable name (e.g. { comprensin: "Comprensión" }).
   * ElevenLabs derives the id from the name and strips accented characters in the process,
   * so the id alone is not safe to show to users — the name has to be fetched from the
   * agent config and resolved separately.
   */
  async getEvaluationCriteriaNames(agentId: string): Promise<Record<string, string>> {
    const data = await this.getAgentConfig(agentId);
    const criteria = data?.platform_settings?.evaluation?.criteria;
    const names: Record<string, string> = {};

    if (Array.isArray(criteria)) {
      for (const criterion of criteria) {
        if (criterion?.id && criterion?.name) {
          names[criterion.id] = criterion.name;
        }
      }
    }

    return names;
  }

  /**
   * Get the language configured for an agent (e.g. "es", "en"), read from
   * conversation_config.agent.language, so the frontend can show session messages
   * (end-of-session, etc.) in the bot's actual teaching language instead of a fixed one.
   */
  async getAgentLanguage(agentId: string): Promise<string | null> {
    const data = await this.getAgentConfig(agentId);
    const language = data?.conversation_config?.agent?.language;
    return typeof language === 'string' && language.trim() ? language.trim() : null;
  }

  /**
   * Test API key validity
   */
  async testApiKey(apiKey?: string): Promise<boolean> {
    const startTime = Date.now();
    
    logger.info('🧪 Testing ElevenLabs API key validity', {
      hasProvidedKey: !!apiKey,
      requestTime: new Date().toISOString()
    });

    try {
      const keyToTest = apiKey || await this.getApiKey();
      
      logger.info('🌐 Making API request to ElevenLabs', {
        endpoint: 'user',
        hasApiKey: !!keyToTest
      });
      
      const response = await fetch(
        'https://api.elevenlabs.io/v1/user',
        {
          headers: {
            'xi-api-key': keyToTest,
          },
        }
      );

      const responseTime = Date.now() - startTime;
      const isValid = response.ok;
      
      logger.info('📡 ElevenLabs API response received', {
        status: response.status,
        statusText: response.statusText,
        responseTime: `${responseTime}ms`,
        isValid
      });
      
      if (isValid) {
        logger.info('✅ API key validation successful', {
          responseTime: `${responseTime}ms`
        });
      } else {
        logger.warn('⚠️ API key validation failed', {
          status: response.status,
          statusText: response.statusText,
          responseTime: `${responseTime}ms`
        });
      }

      return isValid;
    } catch (error) {
      const totalTime = Date.now() - startTime;
      
      logger.error('❌ Error testing API key', { 
        error: error.message,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
      return false;
    }
  }

  /**
   * Update API key in database
   */
  async updateApiKey(newApiKey: string): Promise<void> {
    const startTime = Date.now();
    
    logger.info('🔑 Updating ElevenLabs API key in database', {
      newKeyLength: newApiKey.length,
      requestTime: new Date().toISOString()
    });

    try {
      // Test the new API key first
      logger.info('🧪 Testing new API key before saving', {
        keyLength: newApiKey.length
      });
      
      const isValid = await this.testApiKey(newApiKey);
      
      if (!isValid) {
        logger.error('❌ New API key validation failed', {
          keyLength: newApiKey.length
        });
        throw createError('Invalid API key provided', 400);
      }

      logger.info('✅ New API key validated successfully');

      // Deactivate all existing configs
      logger.info('🗑️ Deactivating existing API key configurations');
      
      await prisma.elevenLabsConfig.updateMany({
        where: { isActive: true },
        data: { isActive: false }
      });

      // Create new config
      logger.info('📝 Creating new API key configuration');
      
      const newConfig = await prisma.elevenLabsConfig.create({
        data: {
          apiKey: newApiKey,
          isActive: true,
        },
      });

      // Invalidate cache
      this.invalidateApiKeyCache();

      const totalTime = Date.now() - startTime;
      
      logger.info('🎉 API key updated successfully', {
        configId: newConfig.id,
        createdAt: newConfig.createdAt,
        totalTime: `${totalTime}ms`
      });
    } catch (error) {
      const totalTime = Date.now() - startTime;
      
      logger.error('❌ Error updating API key', { 
        error: error.message,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
      throw error;
    }
  }

  /**
   * Mask API key for display purposes (show first 4 characters + asterisks)
   */
  private maskApiKey(apiKey: string): string {
    if (!apiKey || apiKey.length < 4) return 'xi-...';
    return apiKey.substring(0, 4) + '*'.repeat(Math.max(0, apiKey.length - 4));
  }

  /**
   * Get current configuration status
   */
  async getCurrentConfig(): Promise<{ hasApiKey: boolean; isValid: boolean; createdAt?: Date; maskedApiKey?: string }> {
    const startTime = Date.now();
    
    logger.info('📊 Getting current ElevenLabs configuration status');

    try {
      // Get current config from database
      const config = await prisma.elevenLabsConfig.findFirst({
        where: { isActive: true },
        orderBy: { createdAt: 'desc' }
      });

      const hasApiKey = !!(config?.apiKey);
      const actualApiKey = config?.apiKey;
      const maskedApiKey = actualApiKey ? this.maskApiKey(actualApiKey) : undefined;
      
      logger.info('🔍 Configuration status retrieved', {
        hasDatabaseConfig: !!config,
        hasApiKey,
        maskedApiKey
      });

      if (!hasApiKey) {
        logger.warn('⚠️ No API key configured');
        return { hasApiKey: false, isValid: false };
      }

      // Test API key validity
      logger.info('🧪 Testing API key validity');
      
      const isValid = await this.testApiKey();
      
      const totalTime = Date.now() - startTime;
      
      logger.info('✅ Configuration status determined', {
        hasApiKey,
        isValid,
        createdAt: config?.createdAt,
        totalTime: `${totalTime}ms`
      });

      return {
        hasApiKey,
        isValid,
        createdAt: config?.createdAt,
        maskedApiKey,
      };
    } catch (error) {
      const totalTime = Date.now() - startTime;
      
      logger.error('❌ Error getting configuration status', { 
        error: error.message,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
      
      return { hasApiKey: false, isValid: false };
    }
  }

  /**
   * Generate conversation feedback from ElevenLabs
   */
  async generateConversationFeedback(conversationId: string, transcript: string): Promise<{
    summary: string;
    analysis: string;
    feedback: string;
    suggestions: string[];
  }> {
    const startTime = Date.now();
    
    logger.info('📝 Generating conversation feedback from ElevenLabs', {
      conversationId,
      transcriptLength: transcript.length,
      requestTime: new Date().toISOString()
    });

    try {
      const apiKey = await this.getApiKey();
      
      logger.info('🌐 Making API request to ElevenLabs for feedback', {
        conversationId,
        endpoint: 'conversation/feedback',
        hasApiKey: !!apiKey
      });
      
      const response = await fetch(
        `https://api.elevenlabs.io/v1/convai/conversation/feedback`,
        {
          method: 'POST',
          headers: {
            'xi-api-key': apiKey,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            conversation_id: conversationId,
            transcript: transcript,
            analysis_type: 'language_learning',
            target_language: 'english',
            native_language: 'spanish'
          })
        }
      );

      const responseTime = Date.now() - startTime;
      
      logger.info('📡 ElevenLabs feedback API response received', {
        conversationId,
        status: response.status,
        statusText: response.statusText,
        responseTime: `${responseTime}ms`,
        hasResponse: !!response
      });

      if (!response.ok) {
        logger.error('❌ Failed to get feedback from ElevenLabs', {
          conversationId,
          status: response.status,
          statusText: response.statusText,
          responseTime: `${responseTime}ms`
        });
        throw createError(`Failed to get feedback: ${response.status} ${response.statusText}`, response.status);
      }

      const data: any = await response.json();
      
      logger.info('📄 Parsing ElevenLabs feedback response', {
        conversationId,
        hasData: !!data,
        dataKeys: data ? Object.keys(data) : [],
        hasSummary: !!data?.summary,
        hasAnalysis: !!data?.analysis,
        hasFeedback: !!data?.feedback
      });
      
      if (!data.summary || !data.analysis || !data.feedback) {
        logger.error('❌ Invalid feedback response from ElevenLabs API', {
          conversationId,
          responseData: data,
          hasSummary: !!data?.summary,
          hasAnalysis: !!data?.analysis,
          hasFeedback: !!data?.feedback
        });
        throw createError('Invalid feedback response from ElevenLabs API', 500);
      }

      const totalTime = Date.now() - startTime;
      
      logger.info('✅ Conversation feedback generated successfully', {
        conversationId,
        summaryLength: data.summary?.length || 0,
        analysisLength: data.analysis?.length || 0,
        feedbackLength: data.feedback?.length || 0,
        suggestionsCount: data.suggestions?.length || 0,
        totalTime: `${totalTime}ms`
      });
      
      return {
        summary: data.summary,
        analysis: data.analysis,
        feedback: data.feedback,
        suggestions: data.suggestions || []
      };
    } catch (error) {
      const totalTime = Date.now() - startTime;
      
      logger.error('❌ Error generating conversation feedback', { 
        error: error.message,
        conversationId,
        totalTime: `${totalTime}ms`,
        stack: error.stack
      });
      throw error;
    }
  }

  /**
   * Fetch conversation details from ElevenLabs (transcript, evaluations/analysis)
   */
  async getConversationDetails(conversationId: string): Promise<any> {
    const startTime = Date.now();

    logger.info('🔎 Fetching ElevenLabs conversation details', {
      conversationId,
      requestTime: new Date().toISOString()
    });

    try {
      const apiKey = await this.getApiKey();

      const url = `https://api.elevenlabs.io/v1/convai/conversations/${conversationId}`;

      logger.info('🌐 Making API request to ElevenLabs', {
        conversationId,
        endpoint: 'convai/conversations/{id}',
        hasApiKey: !!apiKey
      });

      const response = await fetch(url, {
        headers: {
          'xi-api-key': apiKey,
        },
      });

      const responseTime = Date.now() - startTime;

      logger.info('📡 ElevenLabs conversation details response', {
        conversationId,
        status: response.status,
        statusText: response.statusText,
        responseTime: `${responseTime}ms`
      });

      if (!response.ok) {
        const errorText = await response.text();
        logger.error('❌ Failed to fetch conversation details', {
          conversationId,
          status: response.status,
          statusText: response.statusText,
          error: errorText
        });
        throw createError(`Failed to fetch conversation details: ${response.status} ${response.statusText}` as string, response.status);
      }

      const data: any = await response.json();

      logger.info('✅ Conversation details retrieved', {
        conversationId,
        hasTranscript: Array.isArray(data?.transcript) && data.transcript.length > 0,
        hasAnalysis: !!data?.analysis,
        hasEvaluations: !!(data?.evaluations || data?.analysis?.evaluations || data?.analysis?.evaluation_results)
      });

      return data;
    } catch (error) {
      const totalTime = Date.now() - startTime;

      logger.error('❌ Error fetching conversation details', {
        error: (error as any).message,
        conversationId,
        totalTime: `${totalTime}ms`,
        stack: (error as any).stack
      });
      throw error;
    }
  }

  /**
   * List conversations for a given agent in ElevenLabs (best-effort; schema may evolve)
   */
  async listConversationsByAgent(agentId: string, pageSize: number = 50): Promise<any[]> {
    const startTime = Date.now();

    logger.info('🔎 Listing ElevenLabs conversations by agent', {
      agentId,
      pageSize,
      requestTime: new Date().toISOString()
    });

    try {
      const apiKey = await this.getApiKey();
      const url = `https://api.elevenlabs.io/v1/convai/conversations?agent_id=${encodeURIComponent(agentId)}&limit=${pageSize}`;

      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'xi-api-key': apiKey,
          'Accept': 'application/json'
        }
      });

      const responseTime = Date.now() - startTime;
      logger.info('📡 ElevenLabs list conversations response', {
        agentId,
        status: response.status,
        statusText: response.statusText,
        responseTime: `${responseTime}ms`
      });

      if (!response.ok) {
        throw createError(`Failed to list conversations: ${response.status} ${response.statusText}`, response.status);
      }

      const data: any = await response.json();
      const conversations = Array.isArray(data?.conversations) ? data.conversations : (Array.isArray(data) ? data : []);
      return conversations;
    } catch (error: any) {
      logger.warn('⚠️ Error listing ElevenLabs conversations by agent', {
        agentId,
        error: error.message
      });
      return [];
    }
  }


}

export const elevenLabsService = ElevenLabsService.getInstance();

