package com.saas.voip.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.saas.shared.core.TenantContext;
import com.saas.tenant.entity.CallCostRecord;
import com.saas.tenant.service.CallCostTrackingService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Locale;

@Service
@RequiredArgsConstructor
@Slf4j
public class TwilioCostService {
    
    @Value("${twilio.account.sid:}")
    private String twilioAccountSid;
    
    @Value("${twilio.auth.token:}")
    private String twilioAuthToken;
    
    private final CallCostTrackingService callCostTrackingService;
    private final ObjectMapper objectMapper;
    private final RestTemplate restTemplate = new RestTemplate();
    
    /**
     * Save call cost from webhook data (fallback for Test Accounts).
     * Used when Twilio API is not accessible (e.g., Test Account Credentials).
     * 
     * @param callSid Twilio Call SID
     * @param priceStr Price from webhook
     * @param currency Currency from webhook
     * @param durationStr Duration from webhook
     * @param timestampStr Timestamp from webhook
     * @param fromNumber Caller phone number
     * @param toNumber Called phone number
     */
    public void saveCallCostFromWebhook(String callSid, String priceStr, String currency, 
                                       String durationStr, String timestampStr,
                                       String fromNumber, String toNumber) {
        log.info("📥 [TwilioCostService] saveCallCostFromWebhook() CALLED - CallSid: {}", callSid);
        log.info("📥 [TwilioCostService] Webhook data - Price: {} {}, Duration: {}s", priceStr, currency, durationStr);
        
        // Use webhook timestamp as both start and end time (approximation)
        String startTimeStr = timestampStr;
        String endTimeStr = timestampStr;
        
        saveCallCost(callSid, priceStr, currency, durationStr, startTimeStr, endTimeStr, fromNumber, toNumber);
    }
    
    /**
     * Fetch call cost from Twilio API and save to admin database (saas_db).
     * 
     * @param callSid Twilio Call SID
     * @param fromNumber Caller phone number
     * @param toNumber Called phone number
     */
    public void fetchAndSaveCallCost(String callSid, String fromNumber, String toNumber) {
        log.info("🚀 [TwilioCostService] fetchAndSaveCallCost() CALLED - CallSid: {}, From: {}, To: {}", 
            callSid, fromNumber, toNumber);
        
        if (twilioAccountSid == null || twilioAccountSid.isEmpty()) {
            log.error("❌ [TwilioCostService] Twilio Account SID not configured - CANNOT fetch call cost!");
            log.error("❌ [TwilioCostService] Please set twilio.account.sid in application properties");
            return;
        }
        
        if (twilioAuthToken == null || twilioAuthToken.isEmpty()) {
            log.error("❌ [TwilioCostService] Twilio Auth Token not configured - CANNOT fetch call cost!");
            log.error("❌ [TwilioCostService] Please set twilio.auth.token in application properties");
            return;
        }
        
        log.info("✅ [TwilioCostService] Twilio credentials configured");
        
        try {
            // Twilio API endpoint for specific call
            String url = String.format(
                "https://api.twilio.com/2010-04-01/Accounts/%s/Calls/%s.json",
                twilioAccountSid,
                callSid
            );
            
            // Create Basic Auth header
            String auth = twilioAccountSid + ":" + twilioAuthToken;
            String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
            
            HttpHeaders headers = new HttpHeaders();
            headers.set("Authorization", "Basic " + encodedAuth);
            headers.set("Accept", "application/json");
            
            HttpEntity<String> entity = new HttpEntity<>(headers);
            
            log.info("🔍 [TwilioCostService] Calling Twilio API: {}", url);
            
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
            
            log.info("📡 [TwilioCostService] Twilio API response status: {}", response.getStatusCode());
            
            if (response.getStatusCode().is2xxSuccessful()) {
                log.info("📄 [TwilioCostService] Twilio API response body: {}", response.getBody());
                
                JsonNode callData = objectMapper.readTree(response.getBody());
                
                String price = callData.has("price") ? callData.get("price").asText() : null;
                String priceUnit = callData.has("price_unit") ? callData.get("price_unit").asText() : null;
                String duration = callData.has("duration") ? callData.get("duration").asText() : null;
                String startTime = callData.has("start_time") ? callData.get("start_time").asText() : null;
                String endTime = callData.has("end_time") ? callData.get("end_time").asText() : null;
                
                log.info("💰 [TwilioCostService] Parsed data - Price: {} {}, Duration: {}s, StartTime: {}, EndTime: {}", 
                    price, priceUnit, duration, startTime, endTime);
                
                saveCallCost(callSid, price, priceUnit, duration, startTime, endTime, fromNumber, toNumber);
            } else {
                log.error("❌ [TwilioCostService] Failed to fetch - HTTP Status: {}, Body: {}", 
                    response.getStatusCode(), response.getBody());
            }
            
        } catch (Exception e) {
            log.error("❌ [TwilioCostService] EXCEPTION while fetching call cost for CallSid: {}", callSid, e);
            log.error("❌ [TwilioCostService] Exception message: {}", e.getMessage());
            log.error("❌ [TwilioCostService] Exception class: {}", e.getClass().getName());
            
            // Check if it's a Test Account Credentials error
            if (e.getMessage() != null && e.getMessage().contains("20008")) {
                log.error("❌ [TwilioCostService] ========================================");
                log.error("❌ [TwilioCostService] ERROR: Test Account Credentials detected!");
                log.error("❌ [TwilioCostService] ========================================");
                log.error("❌ [TwilioCostService] Twilio Test Accounts cannot access call data via API.");
                log.error("❌ [TwilioCostService] NOTE: The date '2010-04-01' in the URL is the Twilio API VERSION, not a real date.");
                log.error("❌ [TwilioCostService] SOLUTIONS:");
                log.error("❌ [TwilioCostService]   1. Upgrade to a paid Twilio account (recommended)");
                log.error("❌ [TwilioCostService]   2. Use webhook data instead of API calls");
                log.error("❌ [TwilioCostService] ========================================");
            }
        }
    }
    
    private void saveCallCost(String callSid, String priceStr, String currency, String durationStr,
                             String startTimeStr, String endTimeStr, String from, String to) {
        log.info("💾 [TwilioCostService] saveCallCost() CALLED - CallSid: {}", callSid);
        log.info("💾 [TwilioCostService] Raw data - Price: {}, Currency: {}, Duration: {}", priceStr, currency, durationStr);
        
        try {
            CallCostRecord costRecord = new CallCostRecord();
            costRecord.setCallSid(callSid);
            costRecord.setProvider("TWILIO");
            
            // Parse and set cost (Twilio returns negative values, we want positive)
            if (priceStr != null && !priceStr.isEmpty() && !priceStr.equals("null")) {
                BigDecimal price = new BigDecimal(priceStr).abs();
                costRecord.setCost(price);
                log.info("💰 [TwilioCostService] Parsed cost: {}", price);
            } else {
                log.warn("⚠️ [TwilioCostService] Price is null or empty!");
            }
            
            // Set currency
            costRecord.setCurrency(currency != null ? currency.toUpperCase() : "USD");
            log.info("💱 [TwilioCostService] Currency: {}", costRecord.getCurrency());
            
            // Parse and set duration
            if (durationStr != null && !durationStr.isEmpty() && !durationStr.equals("null")) {
                costRecord.setCallDurationSeconds(Integer.parseInt(durationStr));
                log.info("⏱️ [TwilioCostService] Duration: {}s", costRecord.getCallDurationSeconds());
            }
            
            // Parse and set timestamps (Twilio format includes timezone)
            if (startTimeStr != null && !startTimeStr.isEmpty() && !startTimeStr.equals("null")) {
                try {
                    // Twilio format: "Tue, 31 Aug 2010 20:36:28 +0000"
                    // Use ENGLISH locale to ensure stable parsing across different JVM locales
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
                    ZonedDateTime zonedStart = ZonedDateTime.parse(startTimeStr, formatter);
                    costRecord.setCallStartTime(zonedStart.toLocalDateTime());
                    log.info("📅 [TwilioCostService] Parsed start time: {}", costRecord.getCallStartTime());
                } catch (Exception e) {
                    log.error("❌ [TwilioCostService] Failed to parse start time: {}", startTimeStr, e);
                }
            }
            
            if (endTimeStr != null && !endTimeStr.isEmpty() && !endTimeStr.equals("null")) {
                try {
                    // Use ENGLISH locale for stable parsing
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
                    ZonedDateTime zonedEnd = ZonedDateTime.parse(endTimeStr, formatter);
                    costRecord.setCallEndTime(zonedEnd.toLocalDateTime());
                    log.info("📅 [TwilioCostService] Parsed end time: {}", costRecord.getCallEndTime());
                } catch (Exception e) {
                    log.error("❌ [TwilioCostService] Failed to parse end time: {}", endTimeStr, e);
                }
            }
            
            costRecord.setFromNumber(from);
            costRecord.setToNumber(to);
            
            log.info("🎯 [TwilioCostService] Setting TenantContext to saas_db for ADMIN database save");
            // CRITICAL: Set TenantContext to saas_db to save costs in ADMIN database ONLY
            TenantContext.setTenantId("saas_db");
            
            try {
                log.info("💾 [TwilioCostService] Calling callCostTrackingService.saveCallCost()...");
                callCostTrackingService.saveCallCost(costRecord);
                log.info("✅ [TwilioCostService] Twilio call cost saved to ADMIN database (saas_db)!");
                log.info("✅ [TwilioCostService] CallSid: {}, Cost: {} {}, Duration: {}s", 
                    callSid, costRecord.getCost(), costRecord.getCurrency(), costRecord.getCallDurationSeconds());
            } catch (Exception e) {
                log.error("❌ [TwilioCostService] EXCEPTION while saving to database!", e);
                log.error("❌ [TwilioCostService] Exception message: {}", e.getMessage());
                throw e;
            } finally {
                TenantContext.clear();
                log.info("🧹 [TwilioCostService] TenantContext cleared");
            }
            
        } catch (Exception e) {
            log.error("❌ [TwilioCostService] EXCEPTION in saveCallCost() for CallSid: {}", callSid, e);
            log.error("❌ [TwilioCostService] Exception type: {}, Message: {}", e.getClass().getName(), e.getMessage());
        }
    }
}
