package com.saas.voip.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.enums.Provider;
import com.saas.tenant.entity.InboundCallData;
import com.saas.tenant.entity.InboundCallRequest;
import com.saas.tenant.repository.InboundCallDataRepository;
import com.saas.tenant.repository.InboundCallRequestRepository;
import com.saas.tenant.service.InboundCallService;
import com.saas.voip.extractor.ZiwoCallDataExtractor;
import com.saas.voip.service.SmsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;

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

    private final PhoneNumberRepository phoneNumberRepository;
    private final TenantRepository tenantRepository;
    private final InboundCallDataRepository inboundCallDataRepository;
    private final InboundCallRequestRepository inboundCallRequestRepository;
    private final InboundCallService inboundCallService;
    private final ZiwoCallDataExtractor ziwoCallDataExtractor;
    private final SmsService smsService;
    
    @Autowired
    private ObjectMapper objectMapper;

    @PostMapping("/events")
    public ResponseEntity<Map<String, Object>> handleEvent(@RequestBody Map<String, Object> payload) {
        log.info("Received Ziwo event webhook: {}", payload);

        try {
            String event = payload.get("event") != null ? payload.get("event").toString() : null;
            String callId = payload.get("call_id") != null ? payload.get("call_id").toString() : null;

            if (event == null || callId == null) {
                log.warn("Missing event or call_id in Ziwo webhook");
                return ResponseEntity.badRequest().body(Map.of("error", "Missing event or call_id"));
            }

            String toNumber = payload.get("to") != null ? payload.get("to").toString() : null;
            
            if (toNumber == null || toNumber.isEmpty()) {
                log.warn("No 'to' number in Ziwo event webhook");
                return ResponseEntity.badRequest().body(Map.of("error", "Missing 'to' number"));
            }

            TenantContext.clear();

            Optional<PhoneNumber> phoneOpt = phoneNumberRepository.findByPhoneNumber(toNumber);
            
            if (!phoneOpt.isPresent() || phoneOpt.get().getProvider() != Provider.ZIWO) {
                log.warn("No Ziwo phone number found for: {}", toNumber);
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(Map.of("error", "Phone number not configured"));
            }

            PhoneNumber phoneNumber = phoneOpt.get();
            String tenantId = phoneNumber.getTenantId();
            
            Optional<Tenant> tenantOpt = tenantRepository.findByTenantId(tenantId);
            if (!tenantOpt.isPresent()) {
                log.error("Tenant not found for phone number: {}", toNumber);
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(Map.of("error", "Tenant not found"));
            }

            Tenant tenant = tenantOpt.get();
            String schemaName = tenant.getSchemaName();
            
            TenantContext.setTenantId(schemaName);
            log.info("Set tenant context for Ziwo event: {}", schemaName);

            handleEventByType(event, callId, payload);

            TenantContext.clear();

            Map<String, Object> response = new HashMap<>();
            response.put("status", "success");
            response.put("message", "Event processed");

            return ResponseEntity.ok(response);

        } catch (Exception e) {
            log.error("Error handling Ziwo event", e);
            TenantContext.clear();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Map.of("error", "Internal server error"));
        }
    }

    private void handleEventByType(String event, String callId, Map<String, Object> payload) {
        switch (event) {
            case "call.initiated":
            case "call.answered":
            case "call.completed":
            case "call.failed":
            case "call.no-answer":
            case "call.busy":
                handleCallStatusEvent(callId, payload);
                break;

            case "variables.collected":
                handleVariablesCollected(callId, payload);
                break;

            default:
                log.debug("Unhandled Ziwo event type: {}", event);
        }
    }

    private void handleCallStatusEvent(String callId, Map<String, Object> payload) {
        try {
            Optional<InboundCallData> callDataOpt = inboundCallDataRepository.findByCallSid(callId);
            
            if (callDataOpt.isPresent()) {
                InboundCallData callData = callDataOpt.get();
                ziwoCallDataExtractor.updateCallDataWithEvent(callData, payload);
                inboundCallDataRepository.save(callData);
                log.info("Updated Ziwo call data for callId: {}", callId);
            } else {
                log.warn("No call data found for Ziwo callId: {}", callId);
            }
        } catch (Exception e) {
            log.error("Error handling Ziwo call status event", e);
        }
    }

    private void handleVariablesCollected(String callId, Map<String, Object> payload) {
        try {
            @SuppressWarnings("unchecked")
            Map<String, Object> variables = (Map<String, Object>) payload.get("variables");
            
            if (variables == null || variables.isEmpty()) {
                log.warn("No variables in Ziwo event for callId: {}", callId);
                return;
            }

            String nom = getString(variables, "patient_name");
            String telephone = getString(variables, "patient_phone");
            String dateNaissance = getString(variables, "patient_birthdate");
            String maladie = getString(variables, "illness");
            String doctorName = getString(variables, "doctor_choice");
            String appointmentDateStr = getString(variables, "appointment_date");
            String appointmentTimeStr = getString(variables, "appointment_time");
            Boolean confirmed = getBoolean(variables, "confirmation");

            LocalDateTime appointmentDateTime = null;
            if (appointmentDateStr != null && appointmentTimeStr != null) {
                try {
                    String combinedDateTime = appointmentDateStr + "T" + appointmentTimeStr;
                    appointmentDateTime = LocalDateTime.parse(combinedDateTime);
                } catch (DateTimeParseException e) {
                    log.warn("Could not parse appointment date/time: {} {}", appointmentDateStr, appointmentTimeStr);
                }
            }

            @SuppressWarnings("unchecked")
            List<Map<String, Object>> transcript = (List<Map<String, Object>>) payload.get("transcript");
            String conversationTranscript = null;
            if (transcript != null && !transcript.isEmpty()) {
                try {
                    conversationTranscript = objectMapper.writeValueAsString(transcript);
                } catch (JsonProcessingException e) {
                    log.error("Error serializing Ziwo transcript", e);
                }
            }

            InboundCallRequest request = new InboundCallRequest();
            request.setCallSid(callId);
            request.setNom(nom);
            request.setTelephone(telephone);
            request.setDateNaissance(dateNaissance);
            request.setMaladie(maladie);
            request.setDoctorName(doctorName);
            request.setAppointmentDateTime(appointmentDateTime);
            request.setAppointmentConfirmed(confirmed != null && confirmed);
            request.setConversationTranscript(conversationTranscript);
            request.setSmsSent(false);

            inboundCallRequestRepository.save(request);
            log.info("Saved Ziwo inbound call request for callId: {}", callId);

            if (confirmed != null && confirmed && telephone != null && !telephone.isEmpty()) {
                Optional<InboundCallData> callDataOpt = inboundCallDataRepository.findByCallSid(callId);
                if (callDataOpt.isPresent()) {
                    InboundCallData callData = callDataOpt.get();
                    String fromNumber = callData.getToNumber();
                    
                    if (fromNumber != null && !fromNumber.isEmpty()) {
                        String smsSid = smsService.sendAppointmentConfirmation(
                            SmsService.Provider.ZIWO,
                            fromNumber,
                            telephone,
                            nom,
                            doctorName,
                            appointmentDateTime,
                            null
                        );
                        
                        if (smsSid != null) {
                            request.setSmsSent(true);
                            request.setSmsSid(smsSid);
                            inboundCallRequestRepository.save(request);
                            log.info("✅ SMS confirmation sent via Ziwo - SID: {}", smsSid);
                        }
                    }
                }
            }

        } catch (Exception e) {
            log.error("Error handling Ziwo variables.collected event", e);
        }
    }

    private String getString(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value != null ? value.toString() : null;
    }

    private Boolean getBoolean(Map<String, Object> map, String key) {
        Object value = map.get(key);
        if (value == null) {
            return null;
        }
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        return Boolean.parseBoolean(value.toString());
    }
}
