package com.saas.voip.controller;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.tenant.entity.CallCostRecord;
import com.saas.shared.dto.AiCostCalculationResult;
import com.saas.shared.service.ai.AiCostTrackingService;
import com.saas.voip.service.ai.ElevenLabsCostCalculator;
import com.saas.tenant.entity.InboundCallRequest;
import com.saas.tenant.service.CallCostTrackingService;
import com.saas.tenant.service.InboundCallService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * ElevenLabs Post-Call Webhook Controller
 * 
 * Clean Architecture:
 * - Handles ElevenLabs webhooks (presentation layer)
 * - Delegates cost calculation to ElevenLabsCostCalculator (business logic)
 * - Delegates persistence to AiCostTrackingService (data layer)
 * 
 * Dual Cost Tracking:
 * 1. VoIP costs → CallCostRecord (via CallCostTrackingService)
 * 2. AI costs → AiApiCostRecord (via AiCostTrackingService + ElevenLabsCostCalculator)
 * 
 * Both saved to admin database (saas_db) only.
 */
@RestController
@RequestMapping("/api/voip/elevenlabs")
@RequiredArgsConstructor
@Slf4j
public class ElevenLabsPostCallWebhookController {
    
    private final ObjectMapper objectMapper;
    private final InboundCallService inboundCallService;
    private final CallCostTrackingService callCostTrackingService;
    private final PhoneNumberRepository phoneNumberRepository;
    private final TenantRepository tenantRepository;
    
    // Clean Architecture: Dependency injection for AI cost tracking
    private final ElevenLabsCostCalculator elevenLabsCostCalculator;
    private final AiCostTrackingService aiCostTrackingService;
    
    @PostMapping("/post-call-webhook")
    public ResponseEntity<String> handlePostCallWebhook(@RequestBody Map<String, Object> payload) {
        
        try {
            log.info("=== ELEVENLABS POST-CALL WEBHOOK RECEIVED ===");
            log.info("Payload: {}", objectMapper.writeValueAsString(payload));
            
            String eventType = (String) payload.get("type");
            
            if (!"post_call_transcription".equals(eventType)) {
                log.warn("⚠️ Unsupported event type: {}", eventType);
                return ResponseEntity.ok("OK");
            }
            
            Map<String, Object> data = (Map<String, Object>) payload.get("data");
            
            if (data == null) {
                log.warn("⚠️ No data in webhook payload");
                return ResponseEntity.ok("OK");
            }
            
            String conversationId = (String) data.get("conversation_id");
            String agentId = (String) data.get("agent_id");
            
            List<Map<String, Object>> transcript = (List<Map<String, Object>>) data.get("transcript");
            Map<String, Object> metadata = (Map<String, Object>) data.get("metadata");
            Map<String, Object> analysis = (Map<String, Object>) data.get("analysis");
            
            log.info("📞 Conversation ID: {}, Agent ID: {}", conversationId, agentId);
            
            if (metadata != null) {
                Integer callDurationSecs = (Integer) metadata.get("call_duration_secs");
                Integer cost = (Integer) metadata.get("cost"); // Credits
                Long startTimeUnix = ((Number) metadata.get("start_time_unix_secs")).longValue();
                Map<String, Object> dynamicVars = (Map<String, Object>) metadata.get("dynamic_variables");
                
                log.info("⏱️ Duration: {} seconds, Cost: {} credits", callDurationSecs, cost);
                
                String phoneNumber = null;
                String fromNumber = null;
                if (dynamicVars != null) {
                    phoneNumber = (String) dynamicVars.get("To");
                    fromNumber = (String) dynamicVars.get("From");
                }
                
                String tenantIdString = identifyTenantId(phoneNumber);
                String schemaName = identifyTenantSchema(phoneNumber);
                
                if (schemaName == null) {
                    log.warn("⚠️ Cannot identify tenant for phone: {}", phoneNumber);
                    return ResponseEntity.ok("OK");
                }
                
                // Calculate timestamps
                LocalDateTime startTime = LocalDateTime.ofInstant(
                    Instant.ofEpochSecond(startTimeUnix), ZoneId.systemDefault());
                LocalDateTime endTime = startTime.plusSeconds(callDurationSecs);
                
                // Set TenantContext for patient data (uses JPA repository)
                TenantContext.setTenantId(schemaName);
                
                try {
                    savePatientData(conversationId, transcript, analysis);
                    log.info("✅ ElevenLabs patient data saved to tenant schema: {}", schemaName);
                } finally {
                    TenantContext.clear();
                }
                
                // Save VoIP cost record to admin database ONLY
                saveCostRecord(conversationId, callDurationSecs, cost, startTimeUnix, phoneNumber);
                log.info("✅ ElevenLabs VoIP cost record saved to admin database (saas_db)");
                
                // Save AI cost record to admin database ONLY (Clean Architecture)
                saveAiCostRecord(conversationId, agentId, transcript, tenantIdString, 
                                startTime, endTime, fromNumber, phoneNumber);
                log.info("✅ ElevenLabs AI cost record saved to admin database (saas_db)");
            }
            
            return ResponseEntity.ok("OK");
            
        } catch (Exception e) {
            log.error("❌ Error processing ElevenLabs post-call webhook", e);
            return ResponseEntity.status(500).body("Error");
        }
    }
    
    private void savePatientData(String conversationId, List<Map<String, Object>> transcript, 
                                 Map<String, Object> analysis) {
        try {
            if (analysis == null) {
                log.warn("⚠️ No analysis data available");
                return;
            }
            
            List<Map<String, Object>> evaluationResults = 
                (List<Map<String, Object>>) analysis.get("evaluation_criteria_results");
            
            if (evaluationResults == null || evaluationResults.isEmpty()) {
                log.warn("⚠️ No evaluation criteria results");
                return;
            }
            
            InboundCallRequest callRequest = new InboundCallRequest();
            callRequest.setCallSid(conversationId);
            
            for (Map<String, Object> result : evaluationResults) {
                String criteriaName = (String) result.get("criteria");
                Object value = result.get("value");
                
                if (value == null) continue;
                
                if (criteriaName.contains("patient_name") || criteriaName.contains("nom")) {
                    callRequest.setNom(value.toString());
                } else if (criteriaName.contains("phone") || criteriaName.contains("telephone")) {
                    callRequest.setTelephone(value.toString());
                } else if (criteriaName.contains("birthdate") || criteriaName.contains("date_naissance")) {
                    callRequest.setDateNaissance(value.toString());
                } else if (criteriaName.contains("illness") || criteriaName.contains("maladie")) {
                    callRequest.setMaladie(value.toString());
                } else if (criteriaName.contains("doctor") || criteriaName.contains("medecin")) {
                    callRequest.setDoctorName(value.toString());
                } else if (criteriaName.contains("appointment_date")) {
                    callRequest.setMotifVisite(value.toString());
                } else if (criteriaName.contains("confirmed")) {
                    callRequest.setAppointmentConfirmed(Boolean.parseBoolean(value.toString()));
                }
            }
            
            if (transcript != null) {
                callRequest.setConversationTranscript(objectMapper.writeValueAsString(transcript));
            }
            
            inboundCallService.savePatientRequest(callRequest);
            
            log.info("📋 Patient data saved from ElevenLabs - Name: {}, Doctor: {}", 
                    callRequest.getNom(), callRequest.getDoctorName());
            
        } catch (Exception e) {
            log.error("❌ Error saving patient data from ElevenLabs", e);
        }
    }
    
    private void saveCostRecord(String conversationId, Integer durationSecs, Integer credits, 
                                Long startTimeUnix, String phoneNumber) {
        try {
            // Set TenantContext to saas_db to save costs in admin database ONLY
            TenantContext.setTenantId("saas_db");
            
            CallCostRecord costRecord = new CallCostRecord();
            costRecord.setCallSid(conversationId);
            costRecord.setProvider("ELEVENLABS");
            costRecord.setCallDurationSeconds(durationSecs);
            
            BigDecimal costUsd = calculateElevenLabsCost(credits);
            costRecord.setCost(costUsd);
            costRecord.setCurrency("USD");
            
            Map<String, Object> details = new HashMap<>();
            details.put("credits", credits);
            details.put("credits_per_minute", 667);
            costRecord.setCostDetails(details);
            
            LocalDateTime startTime = LocalDateTime.ofInstant(
                Instant.ofEpochSecond(startTimeUnix), ZoneId.systemDefault());
            costRecord.setCallStartTime(startTime);
            costRecord.setCallEndTime(startTime.plusSeconds(durationSecs));
            
            costRecord.setToNumber(phoneNumber);
            
            // Save to admin database (saas_db) - cost data is administrative information
            callCostTrackingService.saveCallCost(costRecord);
            
            log.info("💰 ElevenLabs cost record saved to ADMIN database (saas_db) - {} credits = ${} USD", 
                    credits, costUsd);
            
        } catch (Exception e) {
            log.error("❌ Error saving cost record to admin database", e);
        } finally {
            TenantContext.clear();
        }
    }
    
    private BigDecimal calculateElevenLabsCost(Integer credits) {
        double costPer10kCredits = 1.0;
        double costUsd = (credits / 10000.0) * costPer10kCredits;
        return BigDecimal.valueOf(costUsd).setScale(6, BigDecimal.ROUND_HALF_UP);
    }
    
    /**
     * Save AI cost record to admin database using Clean Architecture
     * 
     * Clean Architecture Flow:
     * 1. Extract usage metrics from transcript (Controller responsibility)
     * 2. Delegate calculation to ElevenLabsCostCalculator (Business logic)
     * 3. Delegate persistence to AiCostTrackingService (Data layer)
     * 
     * @param conversationId ElevenLabs conversation ID (used as call_sid)
     * @param agentId ElevenLabs agent ID
     * @param transcript Full conversation transcript
     * @param tenantId Tenant identifier
     * @param startTime Call start time
     * @param endTime Call end time
     * @param fromNumber Caller phone number
     * @param toNumber Called phone number
     */
    private void saveAiCostRecord(String conversationId, String agentId,
                                   List<Map<String, Object>> transcript, String tenantId,
                                   LocalDateTime startTime, LocalDateTime endTime,
                                   String fromNumber, String toNumber) {
        try {
            if (transcript == null || transcript.isEmpty()) {
                log.debug("💰 [ElevenLabs] No transcript available, skipping AI cost tracking");
                return;
            }
            
            // Count characters from agent messages only (TTS output)
            long totalCharacters = transcript.stream()
                .filter(msg -> "agent".equals(msg.get("role")))
                .mapToLong(msg -> {
                    Object message = msg.get("message");
                    return message != null ? message.toString().length() : 0L;
                })
                .sum();
            
            if (totalCharacters == 0) {
                log.info("💰 [ElevenLabs] No agent messages (zero TTS usage), skipping AI cost tracking");
                return;
            }
            
            log.info("💰 [ElevenLabs] AI Usage - Total TTS characters: {}", totalCharacters);
            
            // Prepare usage metrics for calculator (Clean Architecture: Controller → Calculator)
            Map<String, Object> usageMetrics = new HashMap<>();
            usageMetrics.put("characters", totalCharacters);
            usageMetrics.put("model", "eleven_turbo_v2"); // Default model
            usageMetrics.put("agent_id", agentId);
            usageMetrics.put("conversation_id", conversationId);
            
            // Calculate cost using ElevenLabsCostCalculator (Business Logic Layer)
            AiCostCalculationResult costResult = elevenLabsCostCalculator.calculateCost(usageMetrics);
            
            if (costResult != null && costResult.isValid()) {
                log.info("💰 [ElevenLabs] AI cost calculated: ${} USD for {} characters", 
                        costResult.getCost(), totalCharacters);
                
                // Persist to admin database using AiCostTrackingService (Data Layer)
                aiCostTrackingService.saveAiCost(
                    conversationId,  // call_sid
                    tenantId != null ? tenantId : "default_tenant",
                    costResult,
                    startTime,
                    endTime,
                    fromNumber,
                    toNumber
                );
                
                log.info("✅ [ElevenLabs] AI cost saved to admin database - ConversationId: {}, Tenant: {}", 
                        conversationId, tenantId);
            } else {
                log.warn("⚠️ [ElevenLabs] Invalid cost calculation result for conversation: {}", conversationId);
            }
            
        } catch (Exception e) {
            log.error("❌ [ElevenLabs] Error saving AI cost for conversation: {}", conversationId, e);
        }
    }
    
    /**
     * Identify tenant ID from phone number
     * Returns tenant_id string (not schema name)
     */
    private String identifyTenantId(String phoneNumber) {
        if (phoneNumber == null) {
            return null;
        }
        
        try {
            TenantContext.setTenantId("saas_db");
            
            Optional<PhoneNumber> phoneOpt = phoneNumberRepository.findByPhoneNumber(phoneNumber);
            
            if (phoneOpt.isEmpty()) {
                log.warn("⚠️ [ElevenLabs] Phone number not found in database: {}", phoneNumber);
                return null;
            }
            
            return phoneOpt.get().getTenantId();
            
        } finally {
            TenantContext.clear();
        }
    }
    
    private String identifyTenantSchema(String phoneNumber) {
        if (phoneNumber == null) {
            return null;
        }
        
        try {
            // Set context to saas_db to query admin tables
            TenantContext.setTenantId("saas_db");
            
            Optional<PhoneNumber> phoneOpt = phoneNumberRepository.findByPhoneNumber(phoneNumber);
            
            if (phoneOpt.isEmpty()) {
                return null;
            }
            
            String tenantId = phoneOpt.get().getTenantId();
            Optional<Tenant> tenantOpt = tenantRepository.findByTenantId(tenantId);
            
            if (tenantOpt.isEmpty()) {
                return null;
            }
            
            return tenantOpt.get().getSchemaName();
            
        } finally {
            TenantContext.clear();
        }
    }
}
