package com.saas.voip.controller;

import com.saas.admin.entity.PhoneNumber;
import com.saas.admin.entity.Tenant;
import com.saas.admin.repository.PhoneNumberRepository;
import com.saas.admin.repository.TenantRepository;
import com.saas.shared.core.TenantContext;
import com.saas.shared.dto.VoipConfigDTO;
import com.saas.shared.enums.Provider;
import com.saas.shared.service.TenantVoipConfigRuntimeService;
import com.saas.tenant.entity.InboundCallData;
import com.saas.tenant.entity.InboundCallRequest;
import com.saas.tenant.service.InboundCallService;
import com.saas.voip.service.TelnyxVoiceAIService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;


/**
 * TeXML Controller for Telnyx Voice API
 * TeXML is 100% compatible with Twilio's TwiML
 * Configure this in Telnyx Portal: Voice → TeXML Applications
 */
@RestController
@RequestMapping("/api/voip/telnyx")
@Slf4j
@RequiredArgsConstructor
public class TelnyxTeXMLController {

    private final PhoneNumberRepository phoneNumberRepository;
    private final TenantRepository tenantRepository;
    private final InboundCallService inboundCallService;
    private final TenantVoipConfigRuntimeService voipRuntimeService;
    private final TelnyxVoiceAIService telnyxVoiceAIService;
    private final ObjectMapper objectMapper;
    private final RestTemplate restTemplate = new RestTemplate();
    
    @Value("${telnyx.api.key:#{null}}")
    private String telnyxApiKey;

    /**
     * TeXML Response endpoint - Returns XML like Twilio TwiML
     * Configure in Telnyx Portal: Voice → TeXML Applications → Webhook URL
     * 
     * Example URL: https://your-domain.com/api/voip/telnyx/texml-response
     */
    @PostMapping(value = "/texml-response", produces = MediaType.APPLICATION_XML_VALUE)
    public String handleTeXMLRequest(
            HttpServletRequest request,
            @RequestParam(value = "From", required = false) String from,
            @RequestParam(value = "To", required = false) String to,
            @RequestParam(value = "CallSid", required = false) String callSid,
            @RequestParam(value = "CallStatus", required = false) String callStatus) {
        
        log.info("=== TELNYX TeXML REQUEST ===");
        log.info("📞 From: {}, To: {}, CallSid: {}, Status: {}", from, to, callSid, callStatus);
        
        // CRITICAL: Get tenant info from request attributes (set by TelnyxTenantResolverFilter)
        // The filter already resolved tenant and kept TenantContext as 'saas_db' for EntityManager
        String tenantId = (String) request.getAttribute("RESOLVED_TENANT_ID");
        String tenantSchema = (String) request.getAttribute("RESOLVED_TENANT_SCHEMA");
        
        if (tenantId != null && tenantSchema != null) {
            log.info("✅ Using tenant from filter: {} (schema: {})", tenantId, tenantSchema);
        } else {
            log.warn("⚠️ No tenant info in request attributes - filter may not have resolved tenant");
        }
        
        // Save inbound call data to BOTH admin (saas_db) and tenant databases
        boolean callDataSaved = false;
        if (callSid != null && from != null && to != null && tenantId != null && tenantSchema != null) {
            try {
                InboundCallData callData = InboundCallData.builder()
                        .callSid(callSid)
                        .fromNumber(from)
                        .toNumber(to)
                        .callStatus(callStatus != null ? callStatus : "initiated")
                        .direction("inbound")
                        .provider(Provider.TELNYX.name())  // ✅ CRITICAL: Set provider to TELNYX
                        .startTime(LocalDateTime.now())
                        .build();
                
                // Save to BOTH admin and tenant databases - throws exception if either fails
                inboundCallService.saveInBothDatabases(callData, tenantId, tenantSchema);
                
                callDataSaved = true;
                log.info("✅ SUCCESS: Telnyx call data saved to BOTH admin and tenant databases");
                log.info("   ├─ Admin DB (saas_db): ✅");
                log.info("   ├─ Tenant DB ({}): ✅", tenantSchema);
                log.info("   └─ CallSid: {}", callSid);
            } catch (Exception e) {
                log.error("╔═══════════════════════════════════════════════════════╗");
                log.error("║     CRITICAL ERROR: FAILED TO SAVE CALL DATA         ║");
                log.error("╚═══════════════════════════════════════════════════════╝");
                log.error("❌ Error saving Telnyx call data to databases");
                log.error("   ├─ TenantId: {}", tenantId);
                log.error("   ├─ Schema: {}", tenantSchema);
                log.error("   ├─ CallSid: {}", callSid);
                log.error("   └─ Error: {}", e.getMessage(), e);
                
                // Return error TeXML to signal failure to Telnyx
                return generateErrorTeXML("Database error - please try again");
            }
        }
        
        // If call data save failed, return error instead of proceeding
        if (!callDataSaved && callSid != null) {
            log.error("❌ Call data was NOT saved - returning error TeXML");
            return generateErrorTeXML("Failed to save call data");
        }
        
        // Resolve VoIP configuration (DB first, then fallback to environment)
        // TenantContext is already 'saas_db', so tenant_voip_configs will be read correctly
        Optional<VoipConfigDTO> voipConfig = Optional.empty();
        if (tenantId != null) {
            voipConfig = voipRuntimeService.resolveVoipConfig(tenantId, Provider.TELNYX);
            log.info("📋 VoIP Config loaded for tenant: {} - Type: {}, AssistantID: {}", 
                    tenantId, voipConfig.map(VoipConfigDTO::getAiType).orElse("N/A"),
                    voipConfig.map(VoipConfigDTO::getAiAssistantId).orElse("N/A"));
        }
        
        // Generate TeXML response (compatible with TwiML)
        return generateTeXMLResponse(voipConfig, tenantId, callSid);
    }
    
    /**
     * Generate TeXML/TwiML XML response using tenant VoIP configuration
     * TeXML supports all TwiML verbs: <Say>, <Dial>, <Start>, <Stream>, etc.
     */
    private String generateTeXMLResponse(Optional<VoipConfigDTO> configOpt, String tenantId, String callSid) {
        StringBuilder xml = new StringBuilder();
        xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        xml.append("<Response>\n");
        
        // If no config found or invalid, return error message
        if (configOpt.isEmpty()) {
            log.warn("⚠️ No VoIP configuration found for tenant: {}", tenantId);
            xml.append("  <Say language=\"fr-FR\">Configuration VoIP non trouvée.</Say>\n");
            xml.append("  <Hangup/>\n");
            xml.append("</Response>");
            return xml.toString();
        }
        
        VoipConfigDTO config = configOpt.get();
        
        if (!config.isValid()) {
            log.warn("⚠️ Invalid VoIP configuration for tenant: {}", tenantId);
            xml.append("  <Say language=\"fr-FR\">Configuration VoIP invalide.</Say>\n");
            xml.append("  <Hangup/>\n");
            xml.append("</Response>");
            return xml.toString();
        }
        
        String aiType = config.getAiType();
        String aiAssistantId = config.getAiAssistantId();
        String streamUrl = config.getStreamUrl();
        
        log.info("🔧 VoIP Config - Type: {}, AssistantID: {}, StreamURL: {}, Source: {}", 
                 aiType, aiAssistantId, streamUrl, 
                 config.isFromDatabase() ? "DATABASE" : "ENVIRONMENT");
        
        // Option 1: Use Telnyx Native Voice AI with Connect + AIAssistant
        if ("TELNYX_NATIVE_AI".equals(aiType)) {
            if (aiAssistantId != null && !aiAssistantId.isEmpty()) {
                log.info("🤖 Starting Telnyx Native AI Assistant with Connect: {}", aiAssistantId);
                
                // Construct statusCallback URL for post-call details
                String baseUrl = System.getenv("REPLIT_DOMAINS");
                if (baseUrl == null || baseUrl.isEmpty()) {
                    baseUrl = System.getenv("BASE_URL");
                }
                if (baseUrl == null || baseUrl.isEmpty()) {
                    baseUrl = "https://benedictory-provocative-lauralee.ngrok-free.dev";
                }
                
                // Extract first domain if multiple
                if (baseUrl.contains(",")) {
                    baseUrl = baseUrl.split(",")[0].trim();
                }
                
                // Ensure https:// prefix
                if (!baseUrl.startsWith("http")) {
                    baseUrl = "https://" + baseUrl;
                }
                
                String statusCallbackUrl = baseUrl + "/api/voip/telnyx/status-callback";
                
                // Use Connect + AIAssistant with statusCallback for call details after hangup
                // Per Telnyx documentation: https://support.telnyx.com/en/articles/4374050
                xml.append("  <!-- Telnyx AI Assistant - Full Conversation Mode -->\n");
                xml.append("  <Connect statusCallback=\"").append(statusCallbackUrl).append("\" ");
                xml.append("statusCallbackEvent=\"initiated ringing answered completed\">\n");
                xml.append("    <AIAssistant id=\"").append(aiAssistantId).append("\"/>\n");
                xml.append("  </Connect>\n");
                
                log.info("📞 StatusCallback configured: {}", statusCallbackUrl);
            } else {
                log.warn("⚠️ AI Assistant ID not configured");
                xml.append("  <Say language=\"fr-FR\">Identifiant de l'assistant vocal non configuré.</Say>\n");
                xml.append("  <Hangup/>\n");
            }
        }
        // Option 2: Use WebSocket streaming (for OpenAI, ElevenLabs, etc.)
        else if ("WEBSOCKET_STREAM".equals(aiType)) {
            if (streamUrl != null && !streamUrl.isEmpty()) {
                log.info("🌐 Starting WebSocket stream to: {}", streamUrl);
                
                xml.append("  <!-- Stream audio to WebSocket for AI processing -->\n");
                xml.append("  <Start>\n");
                xml.append("    <Stream url=\"").append(streamUrl).append("\">\n");
                xml.append("      <Parameter name=\"tenantId\" value=\"").append(tenantId != null ? tenantId : "unknown").append("\"/>\n");
                xml.append("      <Parameter name=\"callSid\" value=\"").append(callSid != null ? callSid : "unknown").append("\"/>\n");
                xml.append("    </Stream>\n");
                xml.append("  </Start>\n");
                
                // Keep call alive while streaming AI audio (max 1 hour)
                xml.append("  <Pause length=\"3600\"/>\n");
            } else {
                log.warn("⚠️ Stream URL not configured");
                xml.append("  <Say language=\"fr-FR\">URL de streaming non configurée.</Say>\n");
                xml.append("  <Hangup/>\n");
            }
        }
        // Default: Simple greeting (for testing)
        else {
            log.info("📢 Using default greeting (no specific AI type configured)");
            xml.append("  <Say language=\"fr-FR\">Bonjour, vous êtes connecté au système Telnyx.</Say>\n");
            xml.append("  <Pause length=\"1\"/>\n");
            xml.append("  <Say language=\"fr-FR\">Veuillez configurer l'assistant vocal pour ce tenant.</Say>\n");
            xml.append("  <Hangup/>\n");
        }
        
        xml.append("</Response>");
        
        String response = xml.toString();
        log.info("📄 TeXML Response:\n{}", response);
        
        return response;
    }
    
    /**
     * Generate error TeXML response to signal failure to Telnyx
     * This allows Telnyx to retry or handle the failure appropriately
     */
    private String generateErrorTeXML(String errorMessage) {
        StringBuilder xml = new StringBuilder();
        xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        xml.append("<Response>\n");
        xml.append("  <Say language=\"fr-FR\">Une erreur s'est produite. </Say>\n");
        xml.append("  <Say language=\"fr-FR\">").append(errorMessage).append("</Say>\n");
        xml.append("  <Hangup/>\n");
        xml.append("</Response>");
        
        String response = xml.toString();
        log.error("📄 Error TeXML Response:\n{}", response);
        
        return response;
    }
    
    @GetMapping("/texml-test")
    public String testTeXML() {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
               "<Response>\n" +
               "  <Say language=\"fr-FR\">Test TeXML - Telnyx est correctement configuré.</Say>\n" +
               "</Response>";
    }
    
    /**
     * Status Callback endpoint - Receives call details after hangup
     * Called by Telnyx for events: initiated, ringing, answered, completed
     * 
     * Per Telnyx documentation: https://support.telnyx.com/en/articles/4374050
     */
    @PostMapping("/status-callback")
    public ResponseEntity<Map<String, Object>> handleStatusCallback(
            @RequestParam Map<String, String> params) {
        
        log.info("=== TELNYX STATUS CALLBACK ===");
        log.info("📊 Received parameters: {}", params);
        
        try {
            // Extract call details from Telnyx webhook
            String callSid = params.get("CallSid");
            String to = params.get("To");
            String from = params.get("From");
            String callStatus = params.get("CallStatus");
            String duration = params.get("CallDuration");
            String timestamp = params.get("Timestamp");
            
            log.info("📞 CallSid: {}, Status: {}, Duration: {}s, From: {}, To: {}", 
                     callSid, callStatus, duration, from, to);
            
            if (callSid == null || to == null) {
                log.warn("⚠️ Missing CallSid or To number in status callback");
                return ResponseEntity.ok(Map.of("status", "ignored", "reason", "missing_data"));
            }
            
            // Identify tenant via phone number
            Optional<PhoneNumber> phoneOpt = phoneNumberRepository.findByPhoneNumber(to);
            
            if (phoneOpt.isEmpty() || phoneOpt.get().getProvider() != Provider.TELNYX) {
                log.warn("⚠️ No Telnyx phone number found for: {}", to);
                return ResponseEntity.ok(Map.of("status", "ignored", "reason", "phone_not_found"));
            }
            
            PhoneNumber phoneNumber = phoneOpt.get();
            String tenantId = phoneNumber.getTenantId();
            
            Optional<Tenant> tenantOpt = tenantRepository.findByTenantId(tenantId);
            if (tenantOpt.isEmpty()) {
                log.error("❌ Tenant not found for phone: {}", to);
                return ResponseEntity.ok(Map.of("status", "error", "reason", "tenant_not_found"));
            }
            
            Tenant tenant = tenantOpt.get();
            String schemaName = tenant.getSchemaName();
            
            TenantContext.setTenantId(schemaName);
            log.info("📊 Set tenant context: {}", schemaName);
            
            try {
                // Find existing call record by CallSid
                Optional<InboundCallData> existingCall = 
                    inboundCallService.getCallByCallSid(callSid);
                
                if (existingCall.isPresent()) {
                    // Update existing record with call completion details
                    InboundCallData callData = existingCall.get();
                    callData.setCallStatus(callStatus);
                    
                    if (duration != null && !duration.isEmpty()) {
                        try {
                            callData.setDuration(Integer.parseInt(duration));
                        } catch (NumberFormatException e) {
                            log.warn("⚠️ Invalid duration format: {}", duration);
                        }
                    }
                    
                    if ("completed".equalsIgnoreCase(callStatus)) {
                        callData.setEndTime(LocalDateTime.now());
                        log.info("✅ Call completed - Duration: {}s", duration);
                        
                        // ===== RÉCUPÉRER CONVERSATION TELNYX =====
                        try {
                            log.info("📥 Retrieving conversation transcript for CallSid: {}", callSid);
                            Map<String, Object> conversationData = telnyxVoiceAIService.getConversationTranscript(callSid);
                            
                            if (conversationData != null && !conversationData.isEmpty()) {
                                // Extract messages array from conversation response
                                Object messagesObj = conversationData.get("messages");
                                if (messagesObj != null) {
                                    List<Object> messages = objectMapper.convertValue(messagesObj, new TypeReference<List<Object>>() {});
                                    callData.setConversation(messages);
                                    log.info("✅ Conversation transcript stored ({} messages)", messages.size());
                                    
                                    // Extraire les données patient/RDV de la conversation
                                    extractPatientDataFromConversation(callSid, messages, schemaName);
                                } else {
                                    log.warn("⚠️ No messages found in conversation data");
                                }
                            } else {
                                log.warn("⚠️ No conversation data available for CallSid: {}", callSid);
                            }
                        } catch (Exception convEx) {
                            log.error("❌ Failed to retrieve conversation for CallSid: {}", callSid, convEx);
                        }
                    }
                    
                    // Save to BOTH admin and tenant databases with conversation
                    inboundCallService.saveInBothDatabases(callData, tenantId, schemaName);
                    log.info("✅ Updated call record with status: {}", callStatus);
                    
                } else {
                    log.warn("⚠️ No existing call record found for CallSid: {}", callSid);
                    
                    // Create new record if this is the first callback
                    InboundCallData newCall = InboundCallData.builder()
                            .callSid(callSid)
                            .fromNumber(from)
                            .toNumber(to)
                            .callStatus(callStatus)
                            .direction("inbound")
                            .startTime(LocalDateTime.now())
                            .build();
                    
                    if (duration != null && !duration.isEmpty()) {
                        try {
                            newCall.setDuration(Integer.parseInt(duration));
                        } catch (NumberFormatException e) {
                            log.warn("⚠️ Invalid duration: {}", duration);
                        }
                    }
                    
                    inboundCallService.saveCallData(newCall);
                    log.info("✅ Created new call record from status callback");
                }
                
                return ResponseEntity.ok(Map.of(
                    "status", "success",
                    "message", "Status callback processed",
                    "callStatus", callStatus != null ? callStatus : "unknown"
                ));
                
            } finally {
                TenantContext.clear();
            }
            
        } catch (Exception e) {
            log.error("❌ Error processing status callback", e);
            TenantContext.clear();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Map.of("status", "error", "message", e.getMessage()));
        }
    }
    
    /**
     * Start Telnyx AI Assistant via API call
     * This is the CORRECT way to start an AI assistant from a TeXML webhook
     * 
     * Documentation: https://developers.telnyx.com/docs/voice/programmable-voice/gather-using-ai
     */
    private void startTelnyxAIAssistant(String callControlId, String assistantId) {
        if (telnyxApiKey == null || telnyxApiKey.isEmpty()) {
            log.error("❌ TELNYX_API_KEY not configured! Cannot start AI assistant");
            log.error("💡 Add TELNYX_API_KEY to your .env file or application.properties");
            log.error("💡 Example: TELNYX_API_KEY=KEY0123456789ABCDEF...");
            return;
        }
        
        log.info("✅ Using Telnyx API Key: {}...", telnyxApiKey.substring(0, Math.min(10, telnyxApiKey.length())));
        
        if (callControlId == null || callControlId.isEmpty()) {
            log.error("❌ Call Control ID is null! Cannot start AI assistant");
            return;
        }
        
        try {
            String url = "https://api.telnyx.com/v2/calls/" + callControlId + "/actions/ai_assistant_start";
            
            HttpHeaders headers = new HttpHeaders();
            headers.set("Authorization", "Bearer " + telnyxApiKey);
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            Map<String, Object> payload = new HashMap<>();
            Map<String, String> assistant = new HashMap<>();
            assistant.put("id", assistantId);
            payload.put("assistant", assistant);
            
            HttpEntity<Map<String, Object>> request = new HttpEntity<>(payload, headers);
            
            log.info("🔄 Calling Telnyx API to start AI assistant {} for call {}", assistantId, callControlId);
            
            ResponseEntity<String> response = restTemplate.exchange(
                url,
                HttpMethod.POST,
                request,
                String.class
            );
            
            if (response.getStatusCode().is2xxSuccessful()) {
                log.info("✅ AI Assistant started successfully! Response: {}", response.getBody());
            } else {
                log.error("❌ Failed to start AI assistant. Status: {}, Body: {}", 
                         response.getStatusCode(), response.getBody());
            }
            
        } catch (Exception e) {
            log.error("❌ Error calling Telnyx API to start AI assistant", e);
        }
    }
    
    /**
     * Extract patient data and appointment info from conversation messages
     * and save to InboundCallRequest (tenant DB only)
     */
    private void extractPatientDataFromConversation(String callSid, List<Object> messages, String schemaName) {
        if (messages == null || messages.isEmpty()) {
            log.warn("⚠️ No conversation messages to extract patient data from");
            return;
        }
        
        try {
            log.info("🔍 Extracting patient data from {} conversation messages", messages.size());
            
            // Parse conversation to extract patient/appointment info
            // Look for assistant messages that contain structured data
            String patientName = null;
            String patientPhone = null;
            String illness = null;
            String doctorName = null;
            LocalDateTime appointmentDateTime = null;
            
            for (Object msgObj : messages) {
                if (msgObj instanceof Map) {
                    Map<String, Object> message = (Map<String, Object>) msgObj;
                    String role = (String) message.get("role");
                    String content = (String) message.get("content");
                    
                    if (content != null && !content.isEmpty()) {
                        // Simple text parsing to extract patient info
                        // This is a basic implementation - improve based on actual conversation structure
                        if (patientName == null && content.toLowerCase().contains("nom")) {
                            // Extract name pattern
                            String[] parts = content.split(":");
                            if (parts.length > 1) {
                                patientName = parts[1].trim();
                            }
                        }
                        if (patientPhone == null && content.contains("+") && content.matches(".*\\+\\d{10,15}.*")) {
                            // Extract phone pattern
                            java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\+\\d{10,15}");
                            java.util.regex.Matcher matcher = pattern.matcher(content);
                            if (matcher.find()) {
                                patientPhone = matcher.group();
                            }
                        }
                        // Add more extraction logic as needed
                    }
                    
                    // Check for function calls with structured data
                    if (message.containsKey("function_call")) {
                        Map<String, Object> functionCall = (Map<String, Object>) message.get("function_call");
                        if (functionCall != null && functionCall.containsKey("arguments")) {
                            String argsJson = (String) functionCall.get("arguments");
                            if (argsJson != null) {
                                try {
                                    Map<String, Object> args = objectMapper.readValue(argsJson, new TypeReference<Map<String, Object>>() {});
                                    
                                    // Extract from function arguments
                                    if (args.containsKey("patient_name")) patientName = (String) args.get("patient_name");
                                    if (args.containsKey("patient_phone")) patientPhone = (String) args.get("patient_phone");
                                    if (args.containsKey("illness") || args.containsKey("maladie")) {
                                        illness = (String) args.getOrDefault("illness", args.get("maladie"));
                                    }
                                    if (args.containsKey("doctor_name")) doctorName = (String) args.get("doctor_name");
                                    if (args.containsKey("appointment_datetime")) {
                                        try {
                                            appointmentDateTime = LocalDateTime.parse((String) args.get("appointment_datetime"));
                                        } catch (Exception dtEx) {
                                            log.warn("Failed to parse appointment datetime", dtEx);
                                        }
                                    }
                                } catch (Exception jsonEx) {
                                    log.warn("Failed to parse function arguments JSON", jsonEx);
                                }
                            }
                        }
                    }
                }
            }
            
            // If we found any patient data, save it to tenant DB
            if (patientName != null || patientPhone != null || illness != null) {
                log.info("📝 Patient data extracted - Name: {}, Phone: {}, Illness: {}", 
                         patientName, patientPhone, illness);
                
                // Set tenant context for saving to tenant DB
                TenantContext.setTenantId(schemaName);
                
                try {
                    InboundCallRequest request = new InboundCallRequest();
                    request.setCallSid(callSid);
                    request.setNom(patientName);
                    request.setTelephone(patientPhone);
                    request.setMaladie(illness);
                    request.setDoctorName(doctorName);
                    request.setAppointmentDateTime(appointmentDateTime);
                    request.setProvider("TELNYX");
                    
                    // Store conversation as JSON in InboundCallRequest
                    String conversationJson = objectMapper.writeValueAsString(messages);
                    request.setConversationTranscript(conversationJson);
                    
                    inboundCallService.savePatientRequest(request);
                    log.info("✅ Patient data saved to InboundCallRequest (tenant DB only)");
                } finally {
                    TenantContext.clear();
                }
            } else {
                log.info("⏭️ No patient data found in conversation");
            }
            
        } catch (Exception e) {
            log.error("❌ Error extracting patient data from conversation", e);
        }
    }
}
