package com.saas.voip.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.saas.tenant.entity.InboundCallRequest;
import com.saas.tenant.service.InboundCallService;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.net.URI;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Slf4j
public class OpenAIRealtimeService {

    @Value("${openai.api.key}")
    private String openAiApiKey;
    
    @Value("${server.base-url:http://localhost:8000}")
    private String serverBaseUrl;
    
    private final InboundCallService inboundCallService;
    private final TwilioSmsService twilioSmsService;

    public OpenAIRealtimeService(InboundCallService inboundCallService, TwilioSmsService twilioSmsService) {
        this.inboundCallService = inboundCallService;
        this.twilioSmsService = twilioSmsService;
    }

    private static final String OPENAI_WS_URL = "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01";
    //private static final String SYSTEM_MESSAGE = "Vous êtes un assistant médical professionnel et concis spécialisé dans la prise de rendez-vous en semaine et interdit dans les weekend les RDV. Répondez brièvement (1 phrase max), posez une seule question à la fois, et refusez poliment les week-ends en proposant directement un jour en semaine.";
    private static final String SYSTEM_MESSAGE =
        "Vous êtes un assistant médical virtuel professionnel et bienveillant de la Clinique « La Rive Bleue ». Vos fonctions principales a respecté SVP sont :\n" +
                "1. *Prise de rendez-vous.*\n" +
                "2. *Transfert d’appel* vers un service ou une personne. \n" +
                "3. *Conseil de consultation et prise de rendez-vous* pour un malaise ou un mal donné. \n" +
                "4. *Fournir des informations* sur les services, moyens (équipements) et spécialités disponibles lorsque demandé par l'appelant. \n\n" +
                "*Principes de communication et comportement :*\n" +
                "• *Écoute active et réactivité :* Attendez toujours que la personne ait fini de parler avant de répondre. Soyez vigilant à chaque mot. *Arrêtez de parler immédiatement si vous entendez le patient parler.* Demandez-lui poliment de répéter ce que vous n'avez pas entendu afin de bien comprendre. \n" +
                "• *Clarté et concision :* Répondez en une seule phrase claire et polie. Posez une seule question à la fois. \n" +
                "• *Numéros de téléphone et dates :* Assurez-vous de bien comprendre et répéter chaque chiffre ou élément de la date. Épelez lettre par lettre les informations critiques (nom, prénom) et vérifiez chaque chiffre du numéro de téléphone. \n" +
                "• *Ton :* Gardez toujours un ton calme, naturel, professionnel et empathique.\n" +
                "• *Gestion de la parole simultanée :* Si l'appelant commence à parler alors que vous parlez, interrompez immédiatement vos phrases et écoutez attentivement. Ne continuez que lorsque le patient a terminé.\n\n" +
                "*Déroulement de l'interaction :*\n" +
                "1.  *Accueil et motif :* Commencez toujours par demander le motif précis de l'appel ou de la maladie du patient. Par exemple : 'Bonjour et bienvenue à la Clinique La Rive Bleue. Quel est le motif de votre appel aujourd'hui ?'\n\n" +

                "2.  *Gestion des rendez-vous :*\n" +
                "    •   *Disponibilité :* Les rendez-vous sont uniquement du lundi au vendredi. Ils sont pris à partir de J+1 (le lendemain de l'appel). Privilégiez toujours les dates les plus proches. \n" +
                "    •   *Refus week-end :* Refusez poliment toute demande de rendez-vous le week-end et proposez un jour en semaine. Ex: 'Nous ne prenons pas de rendez-vous le week-end, mais je peux vous proposer le plus tôt possible un jour en semaine, par exemple [proposez la date J+1 si c'est un jour de semaine, sinon le lundi suivant]. Cela vous conviendrait-il ?'\n" +
                "    •   *Choix du médecin :* Après avoir identifié la spécialité requise ou le besoin, proposez *toujours un spécialiste et un médecin généraliste* si pertinent, en laissant le choix au patient. \n" +
                "        - *Liste des médecins et spécialités :*\n" +
                "          1. Dre. Élodie Rochat - Cardiologie\n" +
                "          2. Dr. Noah Müller - Dermatologie\n" +
                "          3. Dre. Léa Favre - Gastro-entérologie\n" +
                "          4. Dr. Liam Schmid - Pneumologie\n" +
                "          5. Dre. Sofia Keller - Neurologie\n" +
                "          6. Dr. Gabriel Weber - Endocrinologie\n" +
                "          7. Dre. Clara Meyer - Orthopédie\n" +
                "          8. Dr. Arthur Gerber - Gynécologie\n" +
                "          9. Dre. Alice Fournier - Urologie\n" +
                "          10. Dr. Louis Huber - Pédiatrie\n" +
                "          11. Dre. Zoé Graf - Ophtalmologie\n" +
                "          12. Dr. Samuel Schneider - ORL\n" +
                "          13. Dr. Nathan Baumann - Généraliste\n" +
                "          14. Dre. Léa Fournier - Généraliste\n" +
                "    •   *Collecte des informations patient (après confirmation du rendez-vous) :* Une fois le rendez-vous (date, heure, médecin) confirmé, demandez successivement :\n" +
                "        a.  *Nom complet :* 'Pourriez-vous me donner votre nom et prénom, s'il vous plaît ?'\n" +
                "        b.  *Épellation et confirmation :* Épelez lettre par lettre le nom et le prénom du patient, puis demandez confirmation. Ex: 'Je note [Nom, épelez N-O-M] [Prénom, épelez P-R-E-N-O-M]. Est-ce bien cela ?'\n" +
                "        c.  *Date de naissance :* 'Et quelle est votre date de naissance ?'\n" +
                "        d.  *Confirmation :* Répétez la date de naissance et demandez confirmation. \n" +
                "        e.  *Numéro de téléphone :* 'Enfin, quel est votre numéro de téléphone pour vous joindre ?' *Attendez que le patient ait entièrement prononcé son numéro de téléphone avant de le répéter ou de demander confirmation.*\n" +
                "        f.  *Confirmation du numéro :* Une fois le numéro complet donné, répétez-le et demandez confirmation. Ex: 'J'ai noté le [Numéro de téléphone]. Est-ce exact ?'\n" +
                "        g.  *Récapitulatif final :* 'Je vous confirme donc votre rendez-vous avec [Nom du Médecin] le [Date] à [Heure]. Vos coordonnées sont [Nom Prénom], né(e) le [Date de naissance], numéro de téléphone [Numéro]. Est-ce tout correct ?'\n\n" +

                "3.  *Gestion des transferts d'appel :*\n" +
                "    •   Si le patient demande à parler à un service spécifique ou à une personne, proposez de le rediriger. Ex: 'Je vous mets en relation avec le service concerné / [Nom de la personne], restez en ligne s'il vous plaît.'\n\n" +

                "4.  *Conseil de consultation pour malaises / maux :*\n" +
                "    •   Selon le malaise ou le mal décrit par le patient, proposez un médecin (spécialiste ou généraliste selon pertinence) et prenez rendez-vous. Ex: 'Pour ces symptômes, je vous recommande de consulter le Dr. [Nom du spécialiste ou généraliste]. Souhaitez-vous prendre rendez-vous ?'\n\n" +

                "5.  *Informations sur les services, moyens et spécialités :*\n" +
                "    •   *Services disponibles :*\n" +
                "        -   Envoi d’ambulance. \n" +
                "        -   Envoi d’un médecin à domicile.\n" +
                "    •   *Informations pour services extra :* Pour ces services, les informations nécessaires sont : Nom Prénom, Adresse, Âge et numéro de téléphone. Demandez ces informations successivement et confirmez-les. \n" +
                "    •   *Moyens (Équipements) disponibles :*\n" +
                "        -   *Équipement de Diagnostic et d'Examen :*\n" +
                "            •   Stéthoscope : Auscultation cardiaque, pulmonaire, abdominale.\n" +
                "            •   Tensiomètre : Mesure de la pression artérielle.\n" +
                "            •   Otoscope : Examen du conduit auditif et du tympan.\n" +
                "            •   Ophtalmoscope : Examen du fond d'œil.\n" +
                "            •   Thermomètre médical : Prise de la température corporelle.\n" +
                "            •   Oxymètre de pouls : Mesure de la saturation en oxygène et fréquence cardiaque.\n" +
                "            •   Dermatoscope : Examen approfondi des lésions cutanées.\n" +
                "            •   Marteau à réflexes : Évaluation des réflexes neurologiques.\n" +
                "            •   Électrocardiographe (ECG) : Enregistrement de l'activité électrique du cœur.\n" +
                "            •   Spiromètre : Mesure de la capacité respiratoire.\n" +
                "            •   Glucomètre : Mesure du taux de sucre dans le sang.\n" +
                "            •   Bandelettes urinaires : Analyse d'urine rapide.\n" +
                "            •   Pèse-personne et toise : Mesure du poids, taille, IMC.\n" +
                "            •   Lampe d'examen : Éclairage focalisé pour examens.\n" +
                "            •   Négatoscope : Lecture d'examens d'imagerie.\n" +
                "        -   *Matériel de Soins et Petite Chirurgie :*\n" +
                "            •   Table d'examen : Examen clinique du patient.\n" +
                "            •   Set de petite chirurgie : Réalisation d'actes chirurgicaux mineurs.\n" +
                "            •   Matériel de suture : Suture de plaies.\n" +
                "            •   Bistouri électrique : Coagulation et section de tissus.\n" +
                "            •   Matériel de cryothérapie : Traitement par le froid (verrues, kératoses).\n" +
                "            •   Aspirateur à mucosités : Aspiration des sécrétions respiratoires.\n" +
                "            •   Trousse d'urgence : Gestion des urgences vitales.\n" +
                "            •   Défibrillateur semi-automatique (DSA) : Choc électrique en cas d'arrêt cardiaque.\n" +
                "        -   *Matériel Spécifique et Consommables :*\n" +
                "            •   Spéculums (auriculaires, vaginaux) : Examen des conduits et organes.\n" +
                "            •   Matériel de prélèvement sanguin : Prises de sang.\n" +
                "            •   Abaisse-langues : Examen de la gorge.\n" +
                "            •   Échographe portable : Imagerie par ultrasons.\n" +
                "            •   Autoclave : Stérilisation du matériel.\n" +
                "            •   Chariot de soins : Rangement et déplacement du matériel de soins.\n" +
                "            •   Gants d'examen jetables : Hygiène et protection.\n" +
                "    •   *Réponse aux demandes d'informations :* Si un patient demande des informations sur un service, un équipement ou une spécialité, fournissez la description pertinente de manière claire et concise. Ex: 'Notre clinique dispose d'un échographe portable qui permet de visualiser en temps réel certains organes...' \n\n" +

                "N'oubliez jamais de rester concentré sur les fonctions définies et de guider le patient avec professionnalisme et empathie.";


    private static final String VOICE = "alice";
    
    private static final List<String> LOG_EVENT_TYPES = List.of(
        "response.content.done",
        "rate_limits.updated", 
        "response.done",
        "input_audio_buffer.committed",
        "input_audio_buffer.speech_stopped",
        "input_audio_buffer.speech_started",
        "session.created"
    );

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final Map<String, WebSocketClient> openAIClients = new ConcurrentHashMap<>();
    private final Map<String, String> sessionStreamIds = new ConcurrentHashMap<>();
    private final Map<String, String> sessionCallSids = new ConcurrentHashMap<>();
    private final Map<String, StringBuilder> sessionTranscripts = new ConcurrentHashMap<>();

    public void connectToOpenAI(WebSocketSession twilioSession) {
        try {
            WebSocketClient openAIClient = new WebSocketClient(new URI(OPENAI_WS_URL)) {
                @Override
                public void onOpen(ServerHandshake handshake) {
                    log.info("Connected to OpenAI Realtime API for session: {}", twilioSession.getId());
                    sendSessionUpdate(this);
                }

                @Override
                public void onMessage(String message) {
                    handleOpenAIMessage(twilioSession, message);
                }

                @Override
                public void onClose(int code, String reason, boolean remote) {
                    log.info("Disconnected from OpenAI: {} - {}", code, reason);
                }

                @Override
                public void onError(Exception ex) {
                    log.error("OpenAI WebSocket error", ex);
                }
            };

            openAIClient.addHeader("Authorization", "Bearer " + openAiApiKey);
            openAIClient.addHeader("OpenAI-Beta", "realtime=v1");
            openAIClient.connect();

            openAIClients.put(twilioSession.getId(), openAIClient);
            sessionTranscripts.put(twilioSession.getId(), new StringBuilder("["));

        } catch (Exception e) {
            log.error("Failed to connect to OpenAI", e);
        }
    }

    private void sendSessionUpdate(WebSocketClient client) {
        try {
            ObjectNode sessionUpdate = objectMapper.createObjectNode();
            sessionUpdate.put("type", "session.update");
            
            ObjectNode session = objectMapper.createObjectNode();
            
            ObjectNode turnDetection = objectMapper.createObjectNode();
            turnDetection.put("type", "server_vad");
            session.set("turn_detection", turnDetection);
            
            session.put("input_audio_format", "g711_ulaw");
            session.put("output_audio_format", "g711_ulaw");
            session.put("voice", VOICE);
            session.put("instructions", SYSTEM_MESSAGE);
            session.put("temperature", 0.8);
            session.putArray("modalities").add("text").add("audio");
            
            // Define function for extracting patient data
            ArrayNode tools = session.putArray("tools");
            ObjectNode tool = tools.addObject();
            tool.put("type", "function");
            tool.put("name", "enregistrer_patient");
            tool.put("description", "Enregistre les informations du patient et son rendez-vous une fois toutes les données collectées et confirmées");
            
            ObjectNode parameters = tool.putObject("parameters");
            parameters.put("type", "object");
            
            ObjectNode properties = parameters.putObject("properties");
            
            ObjectNode nom = properties.putObject("nom");
            nom.put("type", "string");
            nom.put("description", "Nom complet du patient");
            
            ObjectNode dateNaissance = properties.putObject("date_naissance");
            dateNaissance.put("type", "string");
            dateNaissance.put("description", "Date de naissance du patient au format JJ/MM/AAAA");
            
            ObjectNode telephone = properties.putObject("telephone");
            telephone.put("type", "string");
            telephone.put("description", "Numéro de téléphone du patient");
            
            ObjectNode maladie = properties.putObject("maladie");
            maladie.put("type", "string");
            maladie.put("description", "Motif de consultation ou maladie du patient");
            
            ObjectNode motifVisite = properties.putObject("motif_visite");
            motifVisite.put("type", "string");
            motifVisite.put("description", "Motif détaillé de la visite ou consultation");
            
            ObjectNode appointmentDateTime = properties.putObject("appointment_date_time");
            appointmentDateTime.put("type", "string");
            appointmentDateTime.put("description", "Date et heure du rendez-vous au format ISO 8601 (YYYY-MM-DDTHH:MM:SS)");
            
            ObjectNode doctorName = properties.putObject("doctor_name");
            doctorName.put("type", "string");
            doctorName.put("description", "Nom complet du médecin pour le rendez-vous");
            
            ObjectNode appointmentConfirmed = properties.putObject("appointment_confirmed");
            appointmentConfirmed.put("type", "boolean");
            appointmentConfirmed.put("description", "true si le rendez-vous est confirmé par le patient, false sinon");
            
            ArrayNode required = parameters.putArray("required");
            required.add("nom");
            required.add("date_naissance");
            required.add("telephone");
            required.add("maladie");
            
            sessionUpdate.set("session", session);

            String message = objectMapper.writeValueAsString(sessionUpdate);
            log.debug("Sending session update: {}", message);
            client.send(message);

        } catch (Exception e) {
            log.error("Error sending session update", e);
        }
    }

    private void handleOpenAIMessage(WebSocketSession twilioSession, String message) {
        try {
            JsonNode response = objectMapper.readTree(message);
            String type = response.get("type").asText();

            if (LOG_EVENT_TYPES.contains(type)) {
                log.debug("Received OpenAI event: {}", type);
            }

            if ("session.updated".equals(type)) {
                log.info("OpenAI session updated successfully");
            }

            // Trigger response generation when speech is committed
            if ("input_audio_buffer.committed".equals(type)) {
                log.info("Speech committed, triggering response generation");
                triggerResponseGeneration(twilioSession);
            }

            if ("response.audio.delta".equals(type) && response.has("delta")) {
                String audioDelta = response.get("delta").asText();
                log.debug("Received audio delta, sending to Twilio");
                sendAudioToTwilio(twilioSession, audioDelta);
            }
            
            // Capture transcript - assistant messages
            if ("response.audio_transcript.done".equals(type) && response.has("transcript")) {
                String transcript = response.get("transcript").asText();
                addToTranscript(twilioSession.getId(), "assistant", transcript);
            }
            
            // Capture transcript - user messages  
            if ("conversation.item.input_audio_transcription.completed".equals(type) && response.has("transcript")) {
                String transcript = response.get("transcript").asText();
                addToTranscript(twilioSession.getId(), "user", transcript);
            }
            
            // Handle function call from OpenAI
            if ("response.function_call_arguments.done".equals(type)) {
                handleFunctionCall(response, twilioSession.getId());
            }

        } catch (Exception e) {
            log.error("Error processing OpenAI message", e);
        }
    }
    
    private void handleFunctionCall(JsonNode response, String sessionId) {
        try {
            String functionName = response.get("name").asText();
            String arguments = response.get("arguments").asText();
            String callSid = response.get("call_sid") != null ? response.get("call_sid").asText() : null;
            
            if ("enregistrer_patient".equals(functionName)) {
                JsonNode patientData = objectMapper.readTree(arguments);
                
                log.info("========================================");
                log.info("📋 DONNÉES PATIENT EXTRAITES :");
                log.info("========================================");
                log.info("👤 Nom : {}", patientData.has("nom") ? patientData.get("nom").asText() : "N/A");
                log.info("🎂 Date de naissance : {}", patientData.has("date_naissance") ? patientData.get("date_naissance").asText() : "N/A");
                log.info("📞 Téléphone : {}", patientData.has("telephone") ? patientData.get("telephone").asText() : "N/A");
                log.info("🏥 Maladie/Motif : {}", patientData.has("maladie") ? patientData.get("maladie").asText() : "N/A");
                log.info("📝 Motif visite : {}", patientData.has("motif_visite") ? patientData.get("motif_visite").asText() : "N/A");
                log.info("📅 RDV Date/Heure : {}", patientData.has("appointment_date_time") ? patientData.get("appointment_date_time").asText() : "N/A");
                log.info("👨‍⚕️ Médecin : {}", patientData.has("doctor_name") ? patientData.get("doctor_name").asText() : "N/A");
                log.info("✅ RDV Confirmé : {}", patientData.has("appointment_confirmed") ? patientData.get("appointment_confirmed").asBoolean() : "N/A");
                log.info("========================================");
                
                // Find callSid from session mapping
                String resolvedCallSid = callSid;
                if (resolvedCallSid == null) {
                    // Try to get from session mapping
                    for (Map.Entry<String, String> entry : sessionCallSids.entrySet()) {
                        resolvedCallSid = entry.getValue();
                        break; // Get the first one (should be only one active)
                    }
                }
                
                if (resolvedCallSid != null) {
                    // Parse appointment date/time if present
                    LocalDateTime appointmentDateTime = null;
                    if (patientData.has("appointment_date_time")) {
                        try {
                            String dateTimeStr = patientData.get("appointment_date_time").asText();
                            appointmentDateTime = LocalDateTime.parse(dateTimeStr);
                        } catch (Exception e) {
                            log.warn("Failed to parse appointment date/time: {}", patientData.get("appointment_date_time").asText());
                        }
                    }
                    
                    // Save patient request to database
                    InboundCallRequest request = InboundCallRequest.builder()
                            .callSid(resolvedCallSid)
                            .nom(patientData.has("nom") ? patientData.get("nom").asText() : null)
                            .dateNaissance(patientData.has("date_naissance") ? patientData.get("date_naissance").asText() : null)
                            .telephone(patientData.has("telephone") ? patientData.get("telephone").asText() : null)
                            .maladie(patientData.has("maladie") ? patientData.get("maladie").asText() : null)
                            .motifVisite(patientData.has("motif_visite") ? patientData.get("motif_visite").asText() : null)
                            .appointmentDateTime(appointmentDateTime)
                            .doctorName(patientData.has("doctor_name") ? patientData.get("doctor_name").asText() : null)
                            .appointmentConfirmed(patientData.has("appointment_confirmed") ? patientData.get("appointment_confirmed").asBoolean() : false)
                            .smsSent(false)
                            .build();
                    
                    InboundCallRequest savedRequest = inboundCallService.savePatientRequest(request);
                    
                    // Get and save conversation transcript
                    String transcript = getConversationTranscript(sessionId);
                    if (transcript != null && !transcript.isEmpty()) {
                        savedRequest.setConversationTranscript(transcript);
                        savedRequest = inboundCallService.savePatientRequest(savedRequest);
                        log.info("📝 Transcription de conversation sauvegardée");
                    }
                    
                    // Send SMS if appointment is confirmed
                    if (Boolean.TRUE.equals(savedRequest.getAppointmentConfirmed()) && 
                        savedRequest.getAppointmentDateTime() != null &&
                        savedRequest.getTelephone() != null &&
                        savedRequest.getDoctorName() != null) {
                        
                        log.info("📱 Envoi du SMS de confirmation de RDV...");
                        
                        // Build status callback URL
                        String statusCallbackUrl = serverBaseUrl + "/api/voip/sms/status-callback";
                        
                        String smsSid = twilioSmsService.sendAppointmentConfirmationSms(
                            savedRequest.getTelephone(),
                            savedRequest.getNom(),
                            savedRequest.getDoctorName(),
                            savedRequest.getAppointmentDateTime(),
                            statusCallbackUrl
                        );
                        
                        if (smsSid != null) {
                            savedRequest.setSmsSent(true);
                            savedRequest.setSmsSid(smsSid);
                            savedRequest.setSmsStatus("queued");
                            inboundCallService.savePatientRequest(savedRequest);
                            log.info("✅ SMS de confirmation envoyé et marqué dans la base de données (SID: {})", smsSid);
                        }
                    }
                } else {
                    log.warn("No callSid found to save patient request");
                }
            }
        } catch (Exception e) {
            log.error("Error handling function call", e);
        }
    }
    
    private void triggerResponseGeneration(WebSocketSession twilioSession) {
        WebSocketClient client = openAIClients.get(twilioSession.getId());
        if (client != null && client.isOpen()) {
            try {
                ObjectNode responseCreate = objectMapper.createObjectNode();
                responseCreate.put("type", "response.create");
                
                ObjectNode responseConfig = objectMapper.createObjectNode();
                responseConfig.putArray("modalities").add("text").add("audio");
                
                responseCreate.set("response", responseConfig);

                String message = objectMapper.writeValueAsString(responseCreate);
                log.debug("Sending response.create: {}", message);
                client.send(message);
            } catch (Exception e) {
                log.error("Error triggering response generation", e);
            }
        }
    }

    public void sendAudioToOpenAI(WebSocketSession twilioSession, String audioPayload) {
        WebSocketClient client = openAIClients.get(twilioSession.getId());
        if (client != null && client.isOpen()) {
            try {
                ObjectNode audioAppend = objectMapper.createObjectNode();
                audioAppend.put("type", "input_audio_buffer.append");
                audioAppend.put("audio", audioPayload);

                client.send(objectMapper.writeValueAsString(audioAppend));
            } catch (Exception e) {
                log.error("Error sending audio to OpenAI", e);
            }
        }
    }

    private void sendAudioToTwilio(WebSocketSession twilioSession, String audioDelta) {
        try {
            if (twilioSession.isOpen()) {
                // Get the real Twilio streamSid
                String streamSid = sessionStreamIds.get(twilioSession.getId());
                if (streamSid == null) {
                    log.warn("No streamSid found for session {}, skipping audio", twilioSession.getId());
                    return;
                }
                
                ObjectNode audioDeltaMessage = objectMapper.createObjectNode();
                audioDeltaMessage.put("event", "media");
                audioDeltaMessage.put("streamSid", streamSid);
                
                ObjectNode media = objectMapper.createObjectNode();
                media.put("payload", audioDelta);  // OpenAI audio is already in correct base64 format
                
                audioDeltaMessage.set("media", media);

                String message = objectMapper.writeValueAsString(audioDeltaMessage);
                log.trace("Sending to Twilio: {}", message);
                twilioSession.sendMessage(new TextMessage(message));
            }
        } catch (Exception e) {
            log.error("Error sending audio to Twilio", e);
        }
    }
    
    public void setStreamSid(String sessionId, String streamSid) {
        sessionStreamIds.put(sessionId, streamSid);
        log.info("Set streamSid {} for session {}", streamSid, sessionId);
    }
    
    public void setCallSid(String sessionId, String callSid) {
        sessionCallSids.put(sessionId, callSid);
        log.info("Set callSid {} for session {}", callSid, sessionId);
    }
    
    public void setCallSidForSession(String sessionId, String callSid) {
        setCallSid(sessionId, callSid);
    }
    
    public void closeConnection(WebSocketSession session) {
        disconnectFromOpenAI(session);
    }

    public void disconnectFromOpenAI(WebSocketSession twilioSession) {
        WebSocketClient client = openAIClients.remove(twilioSession.getId());
        sessionStreamIds.remove(twilioSession.getId());
        sessionCallSids.remove(twilioSession.getId());
        sessionTranscripts.remove(twilioSession.getId());
        if (client != null && client.isOpen()) {
            client.close();
            log.info("Closed OpenAI connection for session: {}", twilioSession.getId());
        }
    }
    
    private void addToTranscript(String sessionId, String role, String content) {
        StringBuilder transcript = sessionTranscripts.get(sessionId);
        if (transcript != null) {
            try {
                if (transcript.length() > 1) {
                    transcript.append(",");
                }
                ObjectNode message = objectMapper.createObjectNode();
                message.put("role", role);
                message.put("content", content);
                message.put("timestamp", LocalDateTime.now().toString());
                transcript.append(objectMapper.writeValueAsString(message));
            } catch (Exception e) {
                log.error("Error adding to transcript", e);
            }
        }
    }
    
    private String getConversationTranscript(String sessionId) {
        StringBuilder transcript = sessionTranscripts.get(sessionId);
        if (transcript != null && transcript.length() > 1) {
            return transcript.toString() + "]";
        }
        return null;
    }
}
