import { Response } from 'express';
import { AuthenticatedRequest } from '../middlewares/auth';
import { prisma } from '../index';
import { elevenLabsService } from '../services/elevenLabsService';
import { openaiService } from '../services/openaiService';
import { BotAccessService } from '../services/botAccessService';
import { createError, asyncHandler } from '../middlewares/errorHandler';
import { resolveEvaluationIdentifiers } from '../utils/evaluationCriteria';
import { z } from 'zod';
import logger from '../utils/logger';

// Validation schemas
const updateApiKeySchema = z.object({
  apiKey: z.string().min(1, 'API key is required'),
});

const agentIdParamSchema = z.object({
  agentId: z.string().min(1, 'Agent ID is required'),
});

const botNameParamSchema = z.object({
  botName: z.string().min(1, 'Bot name is required'),
});

// Get current ElevenLabs configuration (admin only)
export const getConfig = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const config = await elevenLabsService.getCurrentConfig();
  
  logger.info('ElevenLabs configuration retrieved', {
    hasApiKey: config.hasApiKey,
    isValid: config.isValid,
  });
  
  res.json({
    success: true,
    data: {
      hasApiKey: config.hasApiKey,
      isValid: config.isValid,
      configuredAt: config.createdAt,
      maskedApiKey: config.maskedApiKey,
    },
  });
});

// Update ElevenLabs API key (admin only)
export const updateApiKey = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { apiKey } = updateApiKeySchema.parse(req.body);
  
  await elevenLabsService.updateApiKey(apiKey);
  
  logger.info('ElevenLabs API key updated successfully');
  
  res.json({
    success: true,
    message: 'API key updated successfully',
  });
});

// Test current API key (admin only)
export const testApiKey = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const isValid = await elevenLabsService.testApiKey();
  
  logger.info('ElevenLabs API key tested', { isValid });
  
  res.json({
    success: true,
    data: { isValid },
  });
});

// Get signed URL for specific agent (authenticated users)
export const getSignedUrl = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { agentId } = agentIdParamSchema.parse(req.params);
  const user = req.user;
  
  if (!user) {
    throw createError('User not authenticated', 401);
  }
  
  // Validate that the user has access to a bot with this agent ID
  const bot = await prisma.bot.findFirst({
    where: { agentId, isActive: true },
  });
  
  if (!bot) {
    throw createError('Bot not found for this agent ID', 404);
  }
  
  const hasAccess = await BotAccessService.checkBotAccess(user.id, bot.id, user.role);
  if (!hasAccess) {
    throw createError('Access denied to this bot', 403);
  }
  
  const signedUrl = await elevenLabsService.getSignedUrl(agentId);
  
  logger.info('Signed URL retrieved successfully', {
    userId: user.id,
    agentId,
    botId: bot.id,
  });
  
  res.json({
    success: true,
    data: { signedUrl },
  });
});

// Get conversation token for specific agent (authenticated users)
export const getConversationToken = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { agentId } = agentIdParamSchema.parse(req.params);
  const user = req.user;
  
  if (!user) {
    throw createError('User not authenticated', 401);
  }
  
  // Validate that the user has access to a bot with this agent ID
  const bot = await prisma.bot.findFirst({
    where: { agentId, isActive: true },
  });
  
  if (!bot) {
    throw createError('Bot not found for this agent ID', 404);
  }
  
  const hasAccess = await BotAccessService.checkBotAccess(user.id, bot.id, user.role);
  if (!hasAccess) {
    throw createError('Access denied to this bot', 403);
  }
  
  const token = await elevenLabsService.getConversationToken(agentId);
  
  logger.info('Conversation token retrieved successfully', {
    userId: user.id,
    agentId,
    botId: bot.id,
  });
  
  res.json({
    success: true,
    data: { token },
  });
});

// Get signed URL by bot name (authenticated users)
export const getSignedUrlByBotName = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { botName } = botNameParamSchema.parse(req.params);
  const user = req.user;
  
  if (!user) {
    throw createError('User not authenticated', 401);
  }
  
  const bot = await BotAccessService.getBotByNameWithAccess(user.id, botName, user.role);
  const signedUrl = await elevenLabsService.getSignedUrl(bot.agentId);
  
  logger.info('Signed URL retrieved by bot name', {
    userId: user.id,
    botName,
    botId: bot.id,
    agentId: bot.agentId,
  });
  
  res.json({
    success: true,
    data: { signedUrl },
  });
});

// Get conversation token by bot name (authenticated users)
export const getConversationTokenByBotName = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { botName } = botNameParamSchema.parse(req.params);
  const user = req.user;
  
  if (!user) {
    throw createError('User not authenticated', 401);
  }
  
  const bot = await BotAccessService.getBotByNameWithAccess(user.id, botName, user.role);
  const token = await elevenLabsService.getConversationToken(bot.agentId);
  
  logger.info('Conversation token retrieved by bot name', {
    userId: user.id,
    botName,
    botId: bot.id,
    agentId: bot.agentId,
  });
  
  res.json({
    success: true,
    data: { token },
  });
});

// Validate agent ID (admin/teacher only)
export const validateAgentId = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { agentId } = agentIdParamSchema.parse(req.params);
  
  const isValid = await elevenLabsService.validateAgentId(agentId);
  
  logger.info('Agent ID validation result', { agentId, isValid });
  
  res.json({
    success: true,
    data: { isValid },
  });
});

// Generate conversation feedback and store in database
export const generateConversationFeedback = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { conversationId, transcript, botId } = req.body;
  const user = req.user;
  
  if (!user) {
    throw createError('User not authenticated', 401);
  }
  
  if (!conversationId || !transcript || !botId) {
    throw createError('Missing required fields: conversationId, transcript, botId', 400);
  }
  
  // Validate that the user has access to the bot
  const hasAccess = await BotAccessService.checkBotAccess(user.id, botId, user.role);
  if (!hasAccess) {
    throw createError('Access denied to this bot', 403);
  }
  
  // Get the bot to access its agent ID
  const bot = await prisma.bot.findUnique({
    where: { id: botId }
  });
  
  if (!bot) {
    throw createError('Bot not found', 404);
  }
  
  logger.info('Generating conversation feedback', {
    userId: user.id,
    conversationId,
    botId,
    transcriptLength: transcript.length
  });
  
  // Generate feedback from ElevenLabs
  const feedbackData = await elevenLabsService.generateConversationFeedback(conversationId, transcript);
  
  // Get existing conversation to calculate duration
  const existingConversation = await prisma.conversation.findUnique({
    where: { id: conversationId },
    select: { startedAt: true }
  });
  
  const now = new Date();
  const startedAt = existingConversation?.startedAt || now;
  const durationSeconds = Math.floor((now.getTime() - startedAt.getTime()) / 1000);
  
  // Create or update conversation record in database
  const conversation = await prisma.conversation.upsert({
    where: {
      id: conversationId
    },
    update: {
      transcript: transcript,
      summary: feedbackData.summary,
      aiAnalysis: feedbackData.analysis,
      endedAt: now,
      durationSeconds: durationSeconds,
      conversationMode: 'MIXED' // Default to mixed mode
    },
    create: {
      id: conversationId,
      studentId: user.id,
      botId: botId,
      transcript: transcript,
      summary: feedbackData.summary,
      aiAnalysis: feedbackData.analysis,
      startedAt: startedAt,
      endedAt: now,
      durationSeconds: durationSeconds,
      conversationMode: 'MIXED'
    }
  });
  
  logger.info('Conversation feedback generated and stored successfully', {
    userId: user.id,
    conversationId: conversation.id,
    botId,
    hasSummary: !!feedbackData.summary,
    hasAnalysis: !!feedbackData.analysis,
    hasFeedback: !!feedbackData.feedback
  });
  
  res.json({
    success: true,
    data: {
      conversationId: conversation.id,
      summary: feedbackData.summary,
      analysis: feedbackData.analysis,
      feedback: feedbackData.feedback,
      suggestions: feedbackData.suggestions
    }
  });
});

// Pull conversation details from ElevenLabs (admin/teacher)
export const getConversationDetails = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const conversationId = req.params.conversationId as string
  if (!conversationId) {
    throw createError('conversationId is required', 400)
  }

  // Optional header secret to avoid exposing this widely when testing
  const secret = process.env.ELEVENLABS_WEBHOOK_SECRET
  const incoming = req.get('x-elevenlabs-webhook-secret') || req.get('x-webhook-secret')
  if (secret && incoming !== secret) {
    logger.warn('⚠️ Invalid secret on pull conversation details')
    return res.status(401).json({ success: false, message: 'Invalid secret' })
  }

  const data = await elevenLabsService.getConversationDetails(conversationId)
  res.json({ success: true, data })
})

// Ensure feedback: fetch from ElevenLabs using elevenLabsConversationId and store if missing
export const ensureConversationFeedback = asyncHandler(async (req: AuthenticatedRequest, res: Response) => {
  const { conversationId } = req.params as any;
  const body: any = (req.body || {});
  const externalConversationId = body.externalConversationId || body.elevenLabsConversationId || (req.query?.externalConversationId as string) || (req.query?.elevenLabsConversationId as string);
  const forceParam = (req.query?.force ?? body.force ?? '').toString().toLowerCase();
  const force = forceParam === 'true' || forceParam === '1' || forceParam === 'yes';
  const user = req.user;

  if (!user) {
    throw createError('User not authenticated', 401);
  }
  if (!conversationId) {
    throw createError('conversationId is required', 400);
  }

  const conversation = await prisma.conversation.findUnique({ where: { id: conversationId } });
  if (!conversation) {
    throw createError('Conversation not found', 404);
  }

  // Check if user has access to this conversation
  const isOwner = user.id === conversation.studentId;
  const isElevated = user.role === 'ADMIN' || user.role === 'TEACHER';
  if (!isOwner && !isElevated) {
    throw createError('Access denied to this conversation', 403);
  }

  // If already present, return it – unless force refresh requested or analysis looks incomplete
  if (!force && (conversation.summary || conversation.aiAnalysis)) {
    try {
      if (conversation.aiAnalysis) {
        const parsed: any = JSON.parse(conversation.aiAnalysis);
        const hasTranscript = Array.isArray(parsed?.transcript) && parsed.transcript.length > 0;
        const hasEvaluations = Array.isArray(parsed?.evaluations) && parsed.evaluations.length > 0;
        const hasAnalysis = !!parsed?.analysis;
        if (hasTranscript || hasEvaluations || hasAnalysis) {
          return res.json({ success: true, data: conversation });
        }
      }
    } catch { /* fall through and refresh */ }
  }

  const externalIdToUse = conversation.elevenLabsConversationId || externalConversationId;
  let resolvedExternalId = externalIdToUse;

  // Try auto-resolve from ElevenLabs history using the bot's agentId and timestamps
  if (!resolvedExternalId) {
    try {
      const bot = await prisma.bot.findUnique({ where: { id: conversation.botId }, select: { agentId: true } });
      if (bot?.agentId) {
        const list = await elevenLabsService.listConversationsByAgent(bot.agentId, 50);
        // Heurística: escoger la conversación externa más cercana a nuestro rango de tiempo
        const startedAt = conversation.startedAt?.getTime?.() ? conversation.startedAt.getTime() : new Date(conversation.startedAt as any).getTime();
        const endedAt = conversation.endedAt ? (conversation.endedAt as any as Date).getTime?.() ? (conversation.endedAt as any as Date).getTime() : new Date(conversation.endedAt as any).getTime() : undefined;
        let best: any = null;
        let bestScore = Number.POSITIVE_INFINITY;
        for (const c of list) {
          const ts = (c.started_at || c.startedAt || c.created_at || c.createdAt);
          const t = ts ? new Date(ts).getTime() : 0;
          const score = Math.abs(t - (endedAt ?? startedAt));
          if (t && score < bestScore) { bestScore = score; best = c; }
        }
        if (best?.id) {
          resolvedExternalId = best.id;
          logger.info('🔗 Auto-resolved ElevenLabs conversation id from history', { conversationId, resolvedExternalId, scoreMs: bestScore });
          // Persist for futuras consultas
          await prisma.conversation.update({ where: { id: conversation.id }, data: { elevenLabsConversationId: resolvedExternalId } });
        }
      }
    } catch (autoErr: any) {
      logger.warn('⚠️ Auto-resolve external conversation id failed', { conversationId, error: autoErr.message });
    }
  }

  if (!resolvedExternalId) {
    logger.warn('⚠️ No conversation id available for ensure', { conversationId, bodyKeys: Object.keys(body || {}), query: req.query });
    return res.json({ success: false, message: 'No conversation id' });
  }

  logger.info('🔎 Ensuring feedback via pull', { conversationId, elevenLabsConversationId: externalIdToUse, hadStoredId: !!conversation.elevenLabsConversationId, gotFrom: conversation.elevenLabsConversationId ? 'db' : 'request' });
  let details: any = await elevenLabsService.getConversationDetails(resolvedExternalId);

  // Normalize evaluations from multiple possible shapes
  let evaluationMap = details?.analysis?.evaluation_criteria_results
    || details?.analysis?.evaluation_results
    || details?.evaluations
    || {};
  let evaluations = Array.isArray(evaluationMap)
    ? evaluationMap
    : Object.keys(evaluationMap || {}).map((key) => ({
        identifier: key,
        result: evaluationMap[key]?.result || evaluationMap[key]?.status || 'unknown',
        rationale: evaluationMap[key]?.rationale || evaluationMap[key]?.reason || ''
      }));

  if (evaluations.length) {
    const evalBot = await prisma.bot.findUnique({ where: { id: conversation.botId }, select: { agentId: true } });
    evaluations = await resolveEvaluationIdentifiers(evalBot?.agentId, evaluations);
  }

  // Try to capture summary from various fields (overview/summary/call_summary_title)
  let computedSummary = (
    details?.call_summary_title
    || details?.data?.call_summary_title
    || details?.summary
    || details?.overview?.summary
  );
  let summary = computedSummary || 'Evaluation received';

  // Extract transcript into a simple text block as well
  let transcriptArr = details?.transcript || details?.data?.transcript || [];
  let transcriptText = Array.isArray(transcriptArr)
    ? transcriptArr.map((entry: any, i: number) => {
        const role = (entry?.role === 'agent' ? 'Agent' : (entry?.role ? String(entry.role).charAt(0).toUpperCase() + String(entry.role).slice(1) : 'User'));
        const msg = entry?.message || entry?.text || '';
        const t = entry?.time_in_call_secs ? ` (${entry.time_in_call_secs}s)` : '';
        return `${i + 1}. ${role}${t}: ${msg}`;
      }).join('\n')
    : '';

  // If analysis is missing, try to actively request feedback generation using the external id
  if ((!details?.analysis || Object.keys(details.analysis || {}).length === 0) && (!evaluations || (Array.isArray(evaluations) && evaluations.length === 0))) {
    try {
      // If we lack transcript array, rebuild a plain transcript text for the generation endpoint
      const transcriptForGen = transcriptText || '';
      const gen = await elevenLabsService.generateConversationFeedback(resolvedExternalId, transcriptForGen);
      // Merge generated feedback into our fields
      computedSummary = gen?.summary || computedSummary;
      summary = computedSummary || summary;
      evaluations = evaluations && evaluations.length ? evaluations : [];
      transcriptArr = transcriptArr || [];
      details = {
        ...(details || {}),
        analysis: gen?.analysis || details?.analysis || {},
        evaluations: Array.isArray(details?.evaluations) ? details.evaluations : evaluations,
        transcript: transcriptArr
      };
    } catch (genErr: any) {
      logger.warn('⚠️ Fallback generation failed', { conversationId, externalId: resolvedExternalId, error: genErr?.message });
    }
  }

  // F4: translate the transcript summary (and its DB fallback) to Spanish
  const { analysis: translatedAnalysisObj, summary: translatedSummary } =
    await openaiService.translateSummaryFields(details?.analysis, summary);

  // Store full details for later inspection
  const aiAnalysis = JSON.stringify({
    conversation_id: details?.conversation_id || details?.id,
    overview: details?.overview || {},
    call_summary_title: details?.call_summary_title || details?.data?.call_summary_title,
    transcript: transcriptArr,
    analysis: translatedAnalysisObj,
    evaluations,
    raw_payload: details
  }, null, 2);

  const updated = await prisma.conversation.update({
    where: { id: conversation.id },
    data: {
      summary: translatedSummary,
      aiAnalysis,
      elevenLabsConversationId: conversation.elevenLabsConversationId || resolvedExternalId,
      audioTranscript: transcriptText || undefined
    }
  });

  logger.info('✅ Feedback ensured via pull', { conversationId: conversation.id, elevenConvId: conversation.elevenLabsConversationId });
  res.json({ success: true, data: updated });
});

// ElevenLabs webhook: evaluation results
export const webhookEvaluation = asyncHandler(async (req: Request, res: Response) => {
  try {
    const secret = process.env.ELEVENLABS_WEBHOOK_SECRET;
    const incoming = req.headers['x-elevenlabs-webhook-secret'] || req.headers['x-webhook-secret'];
    if (secret && incoming !== secret) {
      logger.warn('⚠️ Invalid webhook secret for evaluation');
      return res.status(401).json({ success: false, message: 'Invalid webhook secret' });
    }

    const payload: any = req.body || {};
    logger.info('📩 levenLabs evaluation webhook received', {
      hasPayload: !!payload,
      keys: Object.keys(payload || {}),
      conversation_id: payload.conversation_id,
      type: payload.type,
      hasTranscript: !!payload.transcript,
      transcriptLength: Array.isArray(payload.transcript) ? payload.transcript.length : 0,
      call_summary_title: payload.call_summary_title
    });

    // Try to resolve conversation identifiers from payload
    const externalConvId = payload.conversation_id || payload.conversationId || payload?.conversation?.id || payload?.data?.conversation_id;
    const internalConvId = payload.internal_conversation_id || payload.id; // optional

    logger.info('🔍 Webhook payload analysis', {
      externalConvId,
      internalConvId,
      payloadKeys: Object.keys(payload || {}),
      payloadType: payload.type || 'unknown'
    });

    // Try to find our conversation by elevenLabsConversationId first, fallback to id
    let conversation = null;
    if (externalConvId) {
      // First try to find by exact elevenLabsConversationId match
      conversation = await prisma.conversation.findFirst({
        where: { elevenLabsConversationId: externalConvId }
      });
      
      // If not found, try to find by conversation ID and update with real ElevenLabs ID
      if (!conversation) {
        conversation = await prisma.conversation.findFirst({
          where: { 
            id: internalConvId,
            elevenLabsConversationId: { startsWith: 'temp_' } // Only update conversations with temp IDs
          }
        });
        
        if (conversation) {
          // Update with real ElevenLabs conversation ID
          await prisma.conversation.update({
            where: { id: conversation.id },
            data: { elevenLabsConversationId: externalConvId }
          });
          logger.info('✅ Updated conversation with real ElevenLabs ID', {
            conversationId: conversation.id,
            oldId: conversation.elevenLabsConversationId,
            newId: externalConvId
          });
        }
      }
    }
    if (!conversation && internalConvId) {
      conversation = await prisma.conversation.findUnique({ where: { id: internalConvId } });
    }

    if (!conversation) {
      logger.warn('⚠️ Conversation not found for evaluation payload', { 
        externalConvId, 
        internalConvId,
        searchByExternalId: !!externalConvId,
        searchByInternalId: !!internalConvId
      });
      // Accept anyway to avoid retries
      return res.json({ success: true });
    }
    
    logger.info('✅ Conversation found for webhook processing', {
      conversationId: conversation.id,
      externalConvId,
      currentElevenLabsId: conversation.elevenLabsConversationId
    });

    // Extract comprehensive data from ElevenLabs webhook
    const transcript = payload.transcript || payload.data?.transcript || [];
    const callSummaryTitle = payload.call_summary_title || payload.data?.call_summary_title || 'Conversation Summary';
    const analysis = payload.analysis || payload.data?.analysis || {};
    const evaluations = payload.evaluations || payload.results || payload.data?.evaluations || [];
    
    logger.info('🔍 Processing webhook data', {
      transcriptLength: Array.isArray(transcript) ? transcript.length : 0,
      callSummaryTitle,
      hasAnalysis: !!analysis,
      evaluationsLength: Array.isArray(evaluations) ? evaluations.length : 0
    });
    
    // Build comprehensive summary
    let summary = '';
    
    // Add call summary title
    if (callSummaryTitle && callSummaryTitle !== 'Conversation Summary') {
      summary += `${callSummaryTitle}\n\n`;
    }
    
    // Add transcript if available
    if (Array.isArray(transcript) && transcript.length > 0) {
      summary += '**Conversation Transcript:**\n';
      transcript.forEach((entry: any, index: number) => {
        const role = entry.role === 'agent' ? 'Agent' : 'User';
        const message = entry.message || entry.text || '';
        const time = entry.time_in_call_secs ? ` (${entry.time_in_call_secs}s)` : '';
        summary += `${index + 1}. ${role}${time}: ${message}\n`;
      });
      summary += '\n';
    }
    
    // Add evaluations if available
    if (Array.isArray(evaluations) && evaluations.length > 0) {
      summary += '**Evaluation Results:**\n';
      evaluations.forEach((e: any) => {
        const identifier = e.identifier || e.id || 'Unknown';
        const result = e.status || e.result || 'unknown';
        const rationale = e.rationale || e.reason || '';
        summary += `• ${identifier}: ${result}`;
        if (rationale) summary += ` (${rationale})`;
        summary += '\n';
      });
    }
    
    // Fallback if no specific data
    if (!summary) {
      summary = payload.summary || 'Conversation completed - no detailed analysis available';
    }

    // F4: translate analysis.transcript_summary to Spanish (the DB `summary` built above is
    // a composite transcript+evaluations dump the frontend already filters out of the
    // "Resumen de la transcripción" tab, so it's left untranslated — not worth the cost).
    const { analysis: translatedAnalysisObj } = await openaiService.translateSummaryFields(analysis, '');

    // Store comprehensive analysis
    const aiAnalysis = JSON.stringify({
      conversation_id: externalConvId,
      call_summary_title: callSummaryTitle,
      transcript: transcript,
      analysis: translatedAnalysisObj,
      evaluations: evaluations,
      raw_payload: payload
    }, null, 2);

    // Update conversation with ElevenLabs data
    await prisma.conversation.update({
      where: { id: conversation.id },
      data: {
        summary,
        aiAnalysis,
        feedbackStatus: 'pending_feedback' // Mark as pending OpenAI feedback
      }
    });

    logger.info('✅ Stored ElevenLabs evaluation on conversation', {
      conversationId: conversation.id,
      externalConvId
    });

    // OpenAI feedback generation is now handled by conversationService.ts
    // to avoid duplicate calls. This controller only handles ElevenLabs data.
    if (false) {
      // No transcript available, mark as completed without OpenAI feedback
      await prisma.conversation.update({
        where: { id: conversation.id },
        data: {
          feedbackStatus: 'completed'
        }
      });
    }

    return res.json({ success: true });
  } catch (err: any) {
    logger.error('❌ Error handling ElevenLabs evaluation webhook', { error: err.message });
    return res.status(500).json({ success: false });
  }
});

