package com.saas.voip.handler;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.saas.voip.service.GeminiRealtimeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Gemini Session Handler - WebSocket Handler for Twilio Media Streams
 * 
 * Manages bidirectional streaming between Twilio and Gemini 2.0 Flash Live API
 * 
 * Flow:
 * 1. Twilio connects to this handler via WebSocket
 * 2. Handler connects to Gemini Realtime API
 * 3. Audio flows: Twilio → Handler → Gemini → Handler → Twilio
 * 4. On disconnect: Save AI costs to admin database
 * 
 * Audio Conversion:
 * - Twilio sends mulaw 8kHz
 * - Gemini expects PCM 16kHz
 * - Gemini returns PCM 24kHz  
 * - Twilio expects mulaw 8kHz
 * 
 * All conversions handled by GeminiRealtimeService
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class GeminiSessionHandler extends TextWebSocketHandler {

    private final GeminiRealtimeService geminiRealtimeService;
    private final ObjectMapper objectMapper;

    private final Map<String, SessionInfo> activeSessions = new ConcurrentHashMap<>();

    /**
     * Session info holder
     */
    private static class SessionInfo {
        String streamSid;
        String callSid;
        String phoneNumber;
        long startTime;
        
        SessionInfo(String streamSid, String callSid, String phoneNumber) {
            this.streamSid = streamSid;
            this.callSid = callSid;
            this.phoneNumber = phoneNumber;
            this.startTime = System.currentTimeMillis();
        }
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String sessionId = session.getId();
        log.info("🔗 [Gemini Handler] Twilio WebSocket connected - Session: {}", sessionId);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        try {
            String payload = message.getPayload();
            JsonNode json = objectMapper.readTree(payload);
            
            String event = json.get("event").asText();
            String sessionId = session.getId();

            switch (event) {
                case "connected":
                    handleConnected(sessionId, json);
                    break;
                
                case "start":
                    handleStart(session, sessionId, json);
                    break;
                
                case "media":
                    handleMedia(sessionId, json);
                    break;
                
                case "stop":
                    handleStop(sessionId, json);
                    break;
                
                default:
                    log.debug("⚪ [Gemini Handler] Unknown event: {}", event);
            }

        } catch (Exception e) {
            log.error("❌ [Gemini Handler] Error handling message", e);
        }
    }

    /**
     * Handle Twilio "connected" event
     */
    private void handleConnected(String sessionId, JsonNode json) {
        log.info("✅ [Gemini Handler] Twilio stream connected - Protocol: {}", 
                json.get("protocol").asText());
    }

    /**
     * Handle Twilio "start" event
     * Initialize Gemini connection and register Twilio session
     */
    private void handleStart(WebSocketSession session, String sessionId, JsonNode json) {
        try {
            JsonNode start = json.get("start");
            String streamSid = start.get("streamSid").asText();
            String callSid = start.get("callSid").asText();
            
            // Extract phone numbers
            JsonNode customParameters = start.get("customParameters");
            String toNumber = customParameters != null && customParameters.has("To") 
                            ? customParameters.get("To").asText() 
                            : null;
            String fromNumber = customParameters != null && customParameters.has("From") 
                              ? customParameters.get("From").asText() 
                              : null;

            log.info("📞 [Gemini Handler] Call started - CallSid: {}, StreamSid: {}", 
                    callSid, streamSid);
            log.info("📞 [Gemini Handler] From: {}, To: {}", fromNumber, toNumber);

            // Save session info
            SessionInfo sessionInfo = new SessionInfo(streamSid, callSid, toNumber);
            activeSessions.put(sessionId, sessionInfo);

            // Register Twilio WebSocket session for audio output
            geminiRealtimeService.registerTwilioSession(streamSid, session);

            // Connect to Gemini with custom instructions
            String systemInstructions = getSystemInstructions(toNumber);
            String voiceName = getVoiceName(toNumber);
            
            boolean connected = geminiRealtimeService.connectToGemini(
                streamSid, 
                systemInstructions, 
                voiceName,
                toNumber,
                callSid
            );

            if (!connected) {
                log.error("❌ [Gemini Handler] Failed to connect to Gemini for call: {}", callSid);
            }

        } catch (Exception e) {
            log.error("❌ [Gemini Handler] Error handling start event", e);
        }
    }

    /**
     * Handle Twilio "media" event
     * Forward audio to Gemini
     */
    private void handleMedia(String sessionId, JsonNode json) {
        try {
            SessionInfo sessionInfo = activeSessions.get(sessionId);
            if (sessionInfo == null) {
                log.warn("⚠️ [Gemini Handler] No active session for: {}", sessionId);
                return;
            }

            JsonNode media = json.get("media");
            String payload = media.get("payload").asText(); // Base64 mulaw audio
            
            log.trace("🎙️ [Gemini Handler] Received audio chunk - Session: {}, Size: {} chars", 
                    sessionId, payload.length());
            
            // Forward audio to Gemini (conversion handled by service)
            geminiRealtimeService.sendAudioToGemini(sessionInfo.streamSid, payload);

        } catch (Exception e) {
            log.error("❌ [Gemini Handler] Error handling media event", e);
        }
    }

    /**
     * Handle Twilio "stop" event
     * Disconnect from Gemini, unregister session, and save costs
     */
    private void handleStop(String sessionId, JsonNode json) {
        try {
            SessionInfo sessionInfo = activeSessions.remove(sessionId);
            if (sessionInfo == null) {
                log.warn("⚠️ [Gemini Handler] No active session for: {}", sessionId);
                return;
            }

            long duration = (System.currentTimeMillis() - sessionInfo.startTime) / 1000;
            log.info("📞 [Gemini Handler] Call ended - CallSid: {}, Duration: {}s", 
                    sessionInfo.callSid, duration);

            // Unregister Twilio session
            geminiRealtimeService.unregisterTwilioSession(sessionInfo.streamSid);

            // Disconnect from Gemini and save AI costs
            geminiRealtimeService.disconnectFromGemini(sessionInfo.streamSid, sessionInfo.callSid);

        } catch (Exception e) {
            log.error("❌ [Gemini Handler] Error handling stop event", e);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String sessionId = session.getId();
        log.info("🔌 [Gemini Handler] Twilio WebSocket closed - Session: {}, Status: {}", 
                sessionId, status);

        // Cleanup any remaining sessions
        SessionInfo sessionInfo = activeSessions.remove(sessionId);
        if (sessionInfo != null) {
            geminiRealtimeService.unregisterTwilioSession(sessionInfo.streamSid);
            geminiRealtimeService.disconnectFromGemini(sessionInfo.streamSid, sessionInfo.callSid);
        }
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        String sessionId = session.getId();
        log.error("❌ [Gemini Handler] Transport error - Session: {}", sessionId, exception);
        
        // Cleanup on error
        SessionInfo sessionInfo = activeSessions.remove(sessionId);
        if (sessionInfo != null) {
            geminiRealtimeService.unregisterTwilioSession(sessionInfo.streamSid);
            geminiRealtimeService.disconnectFromGemini(sessionInfo.streamSid, sessionInfo.callSid);
        }
    }

    /**
     * Get system instructions based on phone number configuration
     * TODO: Load from TenantVoipConfig per phone number
     */
    private String getSystemInstructions(String phoneNumber) {
        // Default French medical assistant instructions
        return """
            Vous êtes un assistant médical professionnel et concis.
            
            Votre rôle:
            - Prendre des rendez-vous médicaux en semaine UNIQUEMENT
            - Refuser poliment les rendez-vous le week-end
            - Poser une seule question à la fois
            - Répondre brièvement (maximum 1-2 phrases)
            - Collecter: nom du patient, téléphone, date de naissance, motif de consultation, médecin souhaité
            
            Instructions:
            - Soyez courtois et professionnel
            - Confirmez toujours les informations collectées
            - Proposez des alternatives si le créneau demandé n'est pas disponible
            - Parlez uniquement en français
            """;
    }

    /**
     * Get voice name based on phone number configuration
     * TODO: Load from TenantVoipConfig per phone number
     */
    private String getVoiceName(String phoneNumber) {
        // Available Gemini voices:
        // - Aoede: Female, warm
        // - Charon: Male, authoritative
        // - Fenrir: Male, bold
        // - Kore: Female, soft
        // - Puck: Male, friendly (default)
        
        return "Puck"; // Default friendly male voice
    }
}
