package com.saas.tenant.service;

import com.saas.tenant.entity.CallCostRecord;
import com.saas.tenant.repository.CallCostRecordRepository;
import jakarta.persistence.EntityManagerFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
@Slf4j
public class CallCostTrackingService {
    
    private final CallCostRecordRepository callCostRecordRepository;
    private final EntityManagerFactory entityManagerFactory;
    
    @Transactional
    public CallCostRecord saveCallCost(CallCostRecord costRecord) {
        log.info("💰 Saving call cost record - CallSid: {}, Provider: {}, Cost: {} {}", 
                costRecord.getCallSid(), costRecord.getProvider(), costRecord.getCost(), costRecord.getCurrency());
        
        Optional<CallCostRecord> existing = callCostRecordRepository.findByCallSid(costRecord.getCallSid());
        if (existing.isPresent()) {
            log.info("📝 Updating existing cost record for CallSid: {}", costRecord.getCallSid());
            CallCostRecord existingRecord = existing.get();
            existingRecord.setCost(costRecord.getCost());
            existingRecord.setCurrency(costRecord.getCurrency());
            existingRecord.setCallDurationSeconds(costRecord.getCallDurationSeconds());
            existingRecord.setCallEndTime(costRecord.getCallEndTime());
            existingRecord.setCostDetails(costRecord.getCostDetails());
            return callCostRecordRepository.save(existingRecord);
        }
        
        return callCostRecordRepository.save(costRecord);
    }
    
    /**
     * Save call cost record in tenant schema using MANUAL Hibernate Session creation.
     * 
     * CRITICAL: This method creates a NEW Hibernate Session with EXPLICIT tenant identifier,
     * bypassing the thread-bound EntityManager from OpenEntityManagerInViewInterceptor.
     * 
     * @param costRecord The cost record to save
     * @param tenantSchema The tenant schema (e.g., "tenant_1")
     */
    public CallCostRecord saveInTenantSchema(CallCostRecord costRecord, String tenantSchema) {
        if (tenantSchema == null || tenantSchema.isBlank()) {
            throw new IllegalArgumentException("tenantSchema cannot be null or blank");
        }
        
        log.info("🆕 Creating MANUAL Hibernate Session for cost record in tenant schema: {}", tenantSchema);
        
        // Create a NEW Session with EXPLICIT tenant identifier
        org.hibernate.Session session = entityManagerFactory.unwrap(org.hibernate.SessionFactory.class)
                .withOptions()
                .tenantIdentifier(tenantSchema)
                .openSession();
        
        try {
            // Start transaction
            session.beginTransaction();
            log.debug("✅ New Hibernate Session created with explicit tenantIdentifier: {}", tenantSchema);
            
            // Check if record already exists
            CallCostRecord existing = session.createQuery(
                    "FROM CallCostRecord WHERE callSid = :callSid", CallCostRecord.class)
                    .setParameter("callSid", costRecord.getCallSid())
                    .uniqueResultOptional()
                    .orElse(null);
            
            if (existing != null) {
                log.info("📝 Updating existing cost record for CallSid: {}", costRecord.getCallSid());
                existing.setCost(costRecord.getCost());
                existing.setCurrency(costRecord.getCurrency());
                existing.setCallDurationSeconds(costRecord.getCallDurationSeconds());
                existing.setCallEndTime(costRecord.getCallEndTime());
                existing.setCostDetails(costRecord.getCostDetails());
                session.merge(existing);
                session.getTransaction().commit();
                
                log.info("💰 Cost record updated in TENANT schema '{}' - CallSid: {}, Cost: {} {}", 
                        tenantSchema, existing.getCallSid(), existing.getCost(), existing.getCurrency());
                return existing;
            } else {
                // Persist new record
                session.persist(costRecord);
                session.flush();
                session.getTransaction().commit();
                
                log.info("💰 Cost record saved to TENANT schema '{}' - CallSid: {}, Cost: {} {}", 
                        tenantSchema, costRecord.getCallSid(), costRecord.getCost(), costRecord.getCurrency());
                return costRecord;
            }
        } catch (Exception e) {
            // Rollback on error
            if (session.getTransaction() != null && session.getTransaction().isActive()) {
                session.getTransaction().rollback();
            }
            log.error("Error saving cost record to tenant schema '{}' for CallSid: {}", 
                    tenantSchema, costRecord.getCallSid(), e);
            throw new RuntimeException("Failed to save cost record to tenant schema: " + tenantSchema, e);
        } finally {
            // Always close the Session
            session.close();
            log.debug("🔒 Closed manual Hibernate Session for schema: {}", tenantSchema);
        }
    }
    
    @Transactional(readOnly = true)
    public Optional<CallCostRecord> getCallCostByCallSid(String callSid) {
        return callCostRecordRepository.findByCallSid(callSid);
    }
    
    @Transactional(readOnly = true)
    public Map<String, Object> getTenantCostReport(LocalDateTime startDate, LocalDateTime endDate) {
        Map<String, Object> report = new HashMap<>();
        
        BigDecimal totalCost = callCostRecordRepository.getTotalCostBetween(startDate, endDate);
        Long callCount = callCostRecordRepository.getCallCountBetween(startDate, endDate);
        List<Object[]> costByProvider = callCostRecordRepository.getCostByProviderBetween(startDate, endDate);
        
        report.put("totalCost", totalCost != null ? totalCost : BigDecimal.ZERO);
        report.put("totalCalls", callCount != null ? callCount : 0L);
        report.put("startDate", startDate);
        report.put("endDate", endDate);
        
        Map<String, BigDecimal> providerCosts = new HashMap<>();
        for (Object[] row : costByProvider) {
            String provider = (String) row[0];
            BigDecimal cost = (BigDecimal) row[1];
            providerCosts.put(provider, cost);
        }
        report.put("costByProvider", providerCosts);
        
        log.info("📊 Cost report generated - Total: {} USD, Calls: {}, Period: {} to {}", 
                totalCost, callCount, startDate, endDate);
        
        return report;
    }
    
    @Transactional(readOnly = true)
    public List<CallCostRecord> getAllCostRecords(LocalDateTime startDate, LocalDateTime endDate) {
        return callCostRecordRepository.findByCallStartTimeBetween(startDate, endDate);
    }
    
    @Transactional(readOnly = true)
    public List<CallCostRecord> getCostRecordsByProvider(String provider) {
        return callCostRecordRepository.findByProvider(provider);
    }

    @Transactional(readOnly = true)
    public Map<String, Object> getTenantCostSummary(String tenantId, LocalDateTime startDate, LocalDateTime endDate) {
        Map<String, Object> summary = new HashMap<>();
        
        LocalDateTime start = startDate != null ? startDate : LocalDateTime.now().minusMonths(1);
        LocalDateTime end = endDate != null ? endDate : LocalDateTime.now();
        
        return getTenantCostReport(start, end);
    }

    @Transactional(readOnly = true)
    public List<CallCostRecord> getCostsByProvider(String provider) {
        return callCostRecordRepository.findByProvider(provider);
    }

    @Transactional(readOnly = true)
    public List<CallCostRecord> getCostsByDateRange(LocalDateTime startDate, LocalDateTime endDate) {
        return callCostRecordRepository.findByCallStartTimeBetween(startDate, endDate);
    }

    @Transactional(readOnly = true)
    public List<CallCostRecord> getAllCosts() {
        String currentTenant = com.saas.shared.core.TenantContext.getTenantId();
        log.debug("🔍 Fetching all costs for tenant schema: {}", currentTenant);
        return callCostRecordRepository.findAll();
    }
}
