package com.saas.voip.controller;

import com.saas.admin.entity.PhoneNumber;
import com.saas.admin.entity.Tenant;
import com.saas.admin.repository.TenantRepository;
import com.saas.admin.service.PhoneNumberService;
import com.saas.shared.core.TenantContext;
import com.saas.shared.dto.VoipConfigDTO;
import com.saas.shared.enums.Provider;
import com.saas.shared.enums.VoiceAiProviderType;
import com.saas.shared.service.BaseUrlResolver;
import com.saas.shared.service.TenantVoipConfigRuntimeService;
import com.saas.tenant.entity.InboundCallData;
import com.saas.voip.config.VoiceAiConfig;
import com.saas.voip.extractor.TwilioCallDataExtractor;
import com.saas.voip.service.TwilioCallBridgeService;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/voip")
@Slf4j
@RequiredArgsConstructor
public class TwilioVoiceController {

    @Value("${server.port:8000}")
    private String serverPort;

    @Value("${voip.ai-bridging.enabled:false}")
    private Boolean aiBridgingEnabled;

    private final TwilioCallDataExtractor twilioCallDataExtractor;
    // private final DualSaveCallDataService dualSaveCallDataService; // Replaced by
    // AsyncCallService
    private final com.saas.shared.service.AsyncCallService asyncCallService;
    private final org.springframework.messaging.simp.SimpMessagingTemplate messagingTemplate;
    private final PhoneNumberService phoneNumberService;
    private final TenantRepository tenantRepository;
    private final VoiceAiConfig voiceAiConfig;
    private final TenantVoipConfigRuntimeService voipConfigRuntimeService;
    private final BaseUrlResolver baseUrlResolver;
    private final TwilioCallBridgeService twilioCallBridgeService;
    private final com.saas.tenant.service.TenantAIConfigService aiConfigService;

    @GetMapping(value = "/incoming-call", produces = MediaType.APPLICATION_XML_VALUE)
    public String handleIncomingCallGet(HttpServletRequest request) {
        return handleIncomingCall(request);
    }

    @PostMapping(value = "/incoming-call", produces = MediaType.APPLICATION_XML_VALUE)
    public String handleIncomingCallPost(HttpServletRequest request) {
        return handleIncomingCall(request);
    }

    private String handleIncomingCall(HttpServletRequest request) {
        String host = request.getHeader("Host");
        String scheme = request.getScheme();
        String forwardedProto = request.getHeader("X-Forwarded-Proto");

        log.info("=== INCOMING CALL FROM TWILIO ===");
        log.info("Host: {}", host);
        log.info("Scheme: {}", scheme);
        log.info("X-Forwarded-Proto: {}", forwardedProto);

        // Extract form data and identify tenant
        MultiValueMap<String, String> formData = extractFormData(request);
        String toNumber = formData.getFirst("To");
        String schemaName = null;

        if (toNumber != null) {
            try {
                // Set admin context for phone number and tenant lookup
                TenantContext.setTenantId("saas_db");

                Optional<String> foundTenantId = phoneNumberService.getTenantIdByPhoneNumber(toNumber);
                if (foundTenantId.isPresent()) {
                    String tenantId = foundTenantId.get();
                    log.info("📞 Identified Tenant ID: {} for phone number: {}", tenantId, toNumber);

                    // Get tenant schema name
                    Optional<Tenant> tenant = tenantRepository.findByTenantId(tenantId);
                    if (tenant.isPresent()) {
                        schemaName = tenant.get().getSchemaName();
                        log.info("📊 Using schema: {} for tenant: {}", schemaName, tenantId);
                    }
                } else {
                    log.warn("⚠️ No tenant found for phone number: {}", toNumber);
                }
            } finally {
                TenantContext.clear();
            }
        }

        // Extract and save call data to BOTH admin + tenant databases
        String tenantId = null;
        if (toNumber != null) {
            try {
                TenantContext.setTenantId("saas_db");
                Optional<String> foundTenantId = phoneNumberService.getTenantIdByPhoneNumber(toNumber);
                if (foundTenantId.isPresent()) {
                    tenantId = foundTenantId.get();
                }
            } finally {
                TenantContext.clear();
            }
        }

        if (schemaName != null && tenantId != null) {
            try {
                InboundCallData callData = twilioCallDataExtractor.extractFromTwilioRequest(formData);
                // Save to BOTH admin (saas_db) and tenant schema asynchronously
                // This prevents blocking the webhook response
                asyncCallService.saveCallDataAsync(callData, schemaName, tenantId);
                log.info("✅ [Async] Triggered saving of Twilio call data");

                // Push WebSocket event to Admin Dashboard
                java.util.Map<String, Object> event = new java.util.HashMap<>();
                event.put("type", "INCOMING_CALL");
                event.put("tenantId", tenantId);
                event.put("from", callData.getFromNumber());
                event.put("to", callData.getToNumber());
                event.put("callSid", callData.getCallSid());
                event.put("timestamp", java.time.LocalDateTime.now().toString());

                messagingTemplate.convertAndSend("/topic/admin/calls", event);
                log.info("📡 WebSocket event sent to /topic/admin/calls");

            } catch (Exception e) {
                log.error("Error triggering async save for Twilio call data", e);
            }
        } else {
            log.warn("⚠️ Skipping call data persistence - no tenant schema/ID identified");
        }

        // Get call parameters from formData
        String fromNumber = formData.getFirst("From");
        String toNumberParam = formData.getFirst("To");
        String callSid = formData.getFirst("CallSid");

        // ========== AI BRIDGING OPTION ==========
        // If AI bridging is enabled, use TwilioCallBridgeService instead of WebSocket
        if (aiBridgingEnabled != null && aiBridgingEnabled && tenantId != null) {
            log.info("🤖 AI Bridging enabled - generating TwiML for AI assistant");

            try {
                String bridgeTwiML = twilioCallBridgeService.generateBridgeTwiML(
                        tenantId,
                        fromNumber,
                        toNumberParam,
                        callSid);

                log.info("✅ AI Bridge TwiML generated successfully");
                return bridgeTwiML;

            } catch (Exception e) {
                log.error("❌ Error generating AI bridge TwiML, falling back to WebSocket", e);
                // Fall through to existing WebSocket logic
            }
        }

        // ========== EXISTING WEBSOCKET LOGIC ==========
        // Resolve AI provider dynamically by phone number FIRST
        VoiceAiProviderType provider = VoiceAiProviderType.OPENAI; // Default fallback

        if (toNumber != null) {
            try {
                TenantContext.setTenantId("saas_db");

                // Get phone number entity to retrieve phone_number_id
                Optional<PhoneNumber> phoneNumberOpt = phoneNumberService.getPhoneNumberByNumber(toNumber);

                if (phoneNumberOpt.isPresent()) {
                    PhoneNumber phoneNumberEntity = phoneNumberOpt.get();
                    Long phoneNumberId = phoneNumberEntity.getId();

                    log.info("📞 Resolved phone_number_id: {} for number: {}", phoneNumberId, toNumber);
                    log.info("🔍 Querying VoIP config with phoneId={}, tenantId={}, provider=TWILIO", phoneNumberId,
                            tenantId);

                    // Resolve VoIP config by phone number
                    Optional<VoipConfigDTO> voipConfigOpt = voipConfigRuntimeService.resolveVoipConfigByPhone(
                            phoneNumberId,
                            tenantId,
                            Provider.TWILIO);

                    if (voipConfigOpt.isPresent()) {
                        VoipConfigDTO voipConfig = voipConfigOpt.get();
                        String aiType = voipConfig.getAiType();

                        log.info("✅ VoIP config resolved - AI Type: {}, Provider: {}", aiType,
                                voipConfig.getProvider());

                        // Map aiType String to VoiceAiProviderType
                        if ("ELEVENLABS".equalsIgnoreCase(aiType)) {
                            provider = VoiceAiProviderType.ELEVENLABS;
                        } else if ("OPENAI".equalsIgnoreCase(aiType)) {
                            provider = VoiceAiProviderType.OPENAI;
                        } else if ("GEMINI".equalsIgnoreCase(aiType)) {
                            // GEMINI with ConversationRelay (text-based, reliable)
                            provider = VoiceAiProviderType.GEMINI;
                            log.info("✅ Gemini provider selected - will use ConversationRelay approach");
                        }
                        // Add more mappings as needed (TELNYX_NATIVE_AI, etc.)
                    } else {
                        log.warn("⚠️ No VoIP config found for phone_number_id: {}, using fallback: {}", phoneNumberId,
                                provider);
                    }
                } else {
                    log.warn("⚠️ Phone number not found in database: {}, using fallback provider", toNumber);
                }
            } catch (Exception e) {
                log.error("❌ Error resolving VoIP config, using fallback provider", e);
            } finally {
                TenantContext.clear();
            }
        }

        log.info("🎯 Final AI provider selected: {}", provider);

        // Route to the correct WebSocket endpoint based on provider using
        // BaseUrlResolver
        String wsUrl;
        if (provider == VoiceAiProviderType.GEMINI) {
            // Gemini: Use ConversationRelay (text-based, reliable)
            wsUrl = baseUrlResolver.buildWebSocketUrl(request, "/api/voip/gemini/conversation");
            log.info("🎯 Routing to Gemini ConversationRelay WebSocket: {}", wsUrl);
        } else {
            // Default: OpenAI, ElevenLabs use /media-stream
            wsUrl = baseUrlResolver.buildWebSocketUrl(request, "/media-stream");
            log.info("🎯 Routing to default WebSocket: {}", wsUrl);
        }

        // Build TwiML based on AI provider
        String twiml;

        if (provider == VoiceAiProviderType.ELEVENLABS) {
            // ElevenLabs: Skip <Say> - let the agent's "first message" speak
            log.info("🎙️ ElevenLabs mode: Agent will deliver welcome message from dashboard");
            twiml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                    "<Response>" +
                    "  <Connect>" +
                    "    <Stream url=\"" + wsUrl + "\">" +
                    "      <Parameter name=\"From\" value=\"" + fromNumber + "\" />" +
                    "      <Parameter name=\"To\" value=\"" + toNumberParam + "\" />" +
                    "    </Stream>" +
                    "  </Connect>" +
                    "</Response>";
        } else if (provider == VoiceAiProviderType.GEMINI) {
            // Gemini: Use ConversationRelay (text-based, TTS/STT handled by Twilio)
            log.info("🎙️ Gemini mode: Using ConversationRelay (text-based)");
            String welcomeGreeting = "Bienvenue à l'Hôpital La Rive Bleue Gemini, un instant s'il vous plaît.";
            twiml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                    "<Response>" +
                    "  <Connect>" +
                    "    <ConversationRelay url=\"" + wsUrl + "\" " +
                    "welcomeGreeting=\"" + welcomeGreeting + "\" " +
                    "language=\"fr-FR\" " +
                    "ttsProvider=\"Google\" " +
                    "voice=\"fr-FR-Standard-A\" />" +
                    "  </Connect>" +
                    "</Response>";
        } else {
            // OpenAI: Use TwiML <Say> for legal disclaimer
            log.info("🎙️ OpenAI mode: TwiML will deliver welcome message");
            twiml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                    "<Response>" +
                    "  <Say voice=\"alloy\" language=\"fr-FR\">" +
                    "    Bonjour, vous êtes en contact avec la Clinique La Rive Bleue. Cet appel est enregistré afin d'améliorer la qualité de nos services."
                    +
                    "  </Say>" +
                    "  <Connect>" +
                    "    <Stream url=\"" + wsUrl + "\">" +
                    "      <Parameter name=\"From\" value=\"" + fromNumber + "\" />" +
                    "      <Parameter name=\"To\" value=\"" + toNumberParam + "\" />" +
                    "    </Stream>" +
                    "  </Connect>" +
                    "</Response>";
        }

        log.info("Returning TwiML for provider {}: {}", provider, twiml);
        return twiml;
    }

    @GetMapping("/health")
    public String health() {
        return "Twilio VoIP Service is running!";
    }

    private MultiValueMap<String, String> extractFormData(HttpServletRequest request) {
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        Enumeration<String> parameterNames = request.getParameterNames();

        while (parameterNames.hasMoreElements()) {
            String paramName = parameterNames.nextElement();
            String[] paramValues = request.getParameterValues(paramName);
            for (String paramValue : paramValues) {
                formData.add(paramName, paramValue);
            }
        }

        return formData;
    }
}
