package com.gnx.telemedicine.controller;

import com.gnx.telemedicine.dto.message.MessageRequestDto;
import com.gnx.telemedicine.dto.message.MessageResponseDto;
import com.gnx.telemedicine.dto.call.CallRequestDto;
import com.gnx.telemedicine.dto.call.CallResponseDto;
import com.gnx.telemedicine.dto.call.WebRtcSignalDto;
import com.gnx.telemedicine.exception.ResourceNotFoundException;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.repository.UserRepository;
import com.gnx.telemedicine.service.MessageService;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;

import java.security.Principal;
import java.util.UUID;

@Controller
@RequiredArgsConstructor
@Slf4j
public class WebSocketMessageController {

    private final MessageService messageService;
    private final SimpMessagingTemplate messagingTemplate;
    private final UserRepository userRepository;

    @MessageMapping("/chat.send")
    public void sendMessage(@Payload MessageRequestDto messageRequest, Principal principal) {
        try {
            String userEmail = principal.getName();
            MessageResponseDto messageResponse = messageService.sendMessage(userEmail, messageRequest);

            // Get receiver email to send message
            UserModel receiver = userRepository.findById(messageRequest.getReceiverId())
                    .orElseThrow(() -> new ResourceNotFoundException("Receiver", messageRequest.getReceiverId()));
            
            // Send to the receiver using their email
            messagingTemplate.convertAndSendToUser(
                    receiver.getEmail(),
                    "/queue/messages",
                    messageResponse
            );

            // Send confirmation back to sender
            messagingTemplate.convertAndSendToUser(
                    userEmail,
                    "/queue/messages",
                    messageResponse
            );

            // Broadcast online status updates
            messagingTemplate.convertAndSend("/topic/online-status", messageResponse);
        } catch (Exception e) {
            log.error("Error sending message via WebSocket", e);
        }
    }

    @MessageMapping("/chat.typing")
    public void handleTyping(@Payload TypingNotification notification, Principal principal) {
        try {
            String userEmail = principal.getName();
            
            // Get sender info
            UserModel sender = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("Sender", userEmail));
            
            // Get receiver email
            UserModel receiver = userRepository.findById(UUID.fromString(notification.getReceiverId()))
                    .orElseThrow(() -> new ResourceNotFoundException("Receiver", notification.getReceiverId()));
            
            // Create typing notification with sender info
            TypingNotification response = new TypingNotification();
            response.setSenderId(sender.getId().toString());
            response.setSenderName(sender.getFirstName() + " " + sender.getLastName());
            response.setReceiverId(receiver.getId().toString());
            response.setTyping(notification.isTyping());
            
            log.info("Typing notification: sender={} ({}), receiver={} ({}), isTyping={}", 
                    sender.getId(), sender.getEmail(), receiver.getId(), receiver.getEmail(), notification.isTyping());
            
            // Send typing status to receiver using their email
            messagingTemplate.convertAndSendToUser(
                    receiver.getEmail(),
                    "/queue/typing",
                    response
            );
            
            log.debug("Typing notification sent from {} to {}: isTyping={}", 
                    userEmail, receiver.getEmail(), notification.isTyping());
        } catch (Exception e) {
            log.error("Error handling typing notification", e);
        }
    }

    @MessageMapping("/call.initiate")
    public void initiateCall(@Payload CallRequestDto callRequest, Principal principal) {
        try {
            log.info("=== CALL INITIATE HANDLER CALLED ===");
            log.info("Principal: {}", principal != null ? principal.getName() : "NULL");
            log.info("CallRequest: {}", callRequest);
            if (callRequest != null) {
                log.info("CallRequest.receiverId: {}, CallRequest.callType: {}", callRequest.getReceiverId(), callRequest.getCallType());
            } else {
                log.error("ERROR: CallRequest is NULL - deserialization may have failed!");
            }
            
            if (callRequest == null) {
                log.error("CallRequest is NULL!");
                return;
            }
            
            if (principal == null) {
                log.error("Principal is NULL - authentication failed!");
                return;
            }
            
            if (callRequest.getReceiverId() == null) {
                log.error("ReceiverId is null in call.initiate");
                return;
            }
            
            String userEmail = principal.getName();
            log.debug("Processing call initiation from: {}", userEmail);
            
            UserModel sender = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("Sender", userEmail));
            
            log.debug("Sender found: {} ({})", sender.getId(), sender.getEmail());
            
            UserModel receiver = userRepository.findById(callRequest.getReceiverId())
                    .orElseThrow(() -> new ResourceNotFoundException("Receiver", callRequest.getReceiverId()));
            
            log.debug("Receiver found: {} ({})", receiver.getId(), receiver.getEmail());

            // Validate that sender is either doctor or patient, and receiver is the opposite role
            if (!((sender.getRole().name().equals("DOCTOR") && receiver.getRole().name().equals("PATIENT")) ||
                  (sender.getRole().name().equals("PATIENT") && receiver.getRole().name().equals("DOCTOR")))) {
                log.warn("Invalid call attempt: {} to {}", sender.getRole(), receiver.getRole());
                return;
            }

            java.util.UUID callId = java.util.UUID.randomUUID();
            
            CallResponseDto callResponse = CallResponseDto.builder()
                    .callId(callId)
                    .senderId(sender.getId())
                    .senderName(sender.getFirstName() + " " + sender.getLastName())
                    .senderAvatarUrl(sender.getAvatarUrl())
                    .receiverId(receiver.getId())
                    .receiverName(receiver.getFirstName() + " " + receiver.getLastName())
                    .receiverAvatarUrl(receiver.getAvatarUrl())
                    .callType(callRequest.getCallType())
                    .callStatus("ringing")
                    .build();

            log.debug("About to send call notification to receiver: {}", receiver.getEmail());
            // Send call notification to receiver
            messagingTemplate.convertAndSendToUser(
                    receiver.getEmail(),
                    "/queue/call",
                    callResponse
            );
            log.info("Call notification sent to receiver: {} at email: {}, destination: /user/{}/queue/call", 
                    receiver.getId(), receiver.getEmail(), receiver.getEmail());

            log.debug("About to send call confirmation to sender: {}", userEmail);
            // Send confirmation to sender
            messagingTemplate.convertAndSendToUser(
                    userEmail,
                    "/queue/call",
                    callResponse
            );
            log.info("Call confirmation sent to sender: {} at email: {}, destination: /user/{}/queue/call", 
                    sender.getId(), userEmail, userEmail);

            log.info("Call initiated from {} ({}) to {} ({})", userEmail, sender.getId(), receiver.getEmail(), receiver.getId());
        } catch (Exception e) {
            log.error("Error initiating call", e);
        }
    }

    @MessageExceptionHandler
    public void handleException(Exception e) {
        log.error("=== WEBSOCKET EXCEPTION HANDLER ===", e);
        log.error("Exception type: {}", e.getClass().getName());
        log.error("Exception message: {}", e.getMessage());
    }

    @MessageMapping("/call.answer")
    public void answerCall(@Payload CallResponseDto callResponse, Principal principal) {
        try {
            String userEmail = principal.getName();
            UserModel user = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));

            UUID otherUserId = user.getId().equals(callResponse.getSenderId()) 
                        ? callResponse.getReceiverId() 
                        : callResponse.getSenderId();
            UserModel otherUser = userRepository.findById(otherUserId)
                    .orElseThrow(() -> new ResourceNotFoundException("User", otherUserId));

            callResponse.setCallStatus("accepted");

            // Notify both parties
            messagingTemplate.convertAndSendToUser(
                    otherUser.getEmail(),
                    "/queue/call",
                    callResponse
            );

            messagingTemplate.convertAndSendToUser(
                    userEmail,
                    "/queue/call",
                    callResponse
            );

            log.info("Call answered by {}", userEmail);
        } catch (Exception e) {
            log.error("Error answering call", e);
        }
    }

    @MessageMapping("/call.reject")
    public void rejectCall(@Payload CallResponseDto callResponse, Principal principal) {
        try {
            String userEmail = principal.getName();
            UserModel user = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));

            UUID otherUserId = user.getId().equals(callResponse.getSenderId()) 
                        ? callResponse.getReceiverId() 
                        : callResponse.getSenderId();
            UserModel otherUser = userRepository.findById(otherUserId)
                    .orElseThrow(() -> new ResourceNotFoundException("User", otherUserId));

            callResponse.setCallStatus("rejected");

            // Notify caller
            messagingTemplate.convertAndSendToUser(
                    otherUser.getEmail(),
                    "/queue/call",
                    callResponse
            );

            messagingTemplate.convertAndSendToUser(
                    userEmail,
                    "/queue/call",
                    callResponse
            );

            log.info("Call rejected by {}", userEmail);
        } catch (Exception e) {
            log.error("Error rejecting call", e);
        }
    }

    @MessageMapping("/call.end")
    public void endCall(@Payload CallResponseDto callResponse, Principal principal) {
        try {
            String userEmail = principal.getName();
            UserModel user = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));

            UUID otherUserId = user.getId().equals(callResponse.getSenderId()) 
                        ? callResponse.getReceiverId() 
                        : callResponse.getSenderId();
            UserModel otherUser = userRepository.findById(otherUserId)
                    .orElseThrow(() -> new ResourceNotFoundException("User", otherUserId));

            callResponse.setCallStatus("ended");

            // Notify both parties
            messagingTemplate.convertAndSendToUser(
                    otherUser.getEmail(),
                    "/queue/call",
                    callResponse
            );

            messagingTemplate.convertAndSendToUser(
                    userEmail,
                    "/queue/call",
                    callResponse
            );

            log.info("Call ended by {}", userEmail);
        } catch (Exception e) {
            log.error("Error ending call", e);
        }
    }

    @MessageMapping("/call.cancel")
    public void cancelCall(@Payload CallResponseDto callResponse, Principal principal) {
        try {
            String userEmail = principal.getName();
            UserModel user = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("User", userEmail));

            UUID otherUserId = user.getId().equals(callResponse.getSenderId()) 
                        ? callResponse.getReceiverId() 
                        : callResponse.getSenderId();
            UserModel otherUser = userRepository.findById(otherUserId)
                    .orElseThrow(() -> new ResourceNotFoundException("User", otherUserId));

            callResponse.setCallStatus("cancelled");

            // Notify receiver
            messagingTemplate.convertAndSendToUser(
                    otherUser.getEmail(),
                    "/queue/call",
                    callResponse
            );

            messagingTemplate.convertAndSendToUser(
                    userEmail,
                    "/queue/call",
                    callResponse
            );

            log.info("Call cancelled by {}", userEmail);
        } catch (Exception e) {
            log.error("Error cancelling call", e);
        }
    }

    @MessageMapping("/call.signal")
    public void handleWebRtcSignal(@Payload WebRtcSignalDto signal, Principal principal) {
        try {
            String userEmail = principal.getName();
            UserModel sender = userRepository.findByEmail(userEmail)
                    .orElseThrow(() -> new ResourceNotFoundException("Sender", userEmail));

            UserModel receiver = userRepository.findById(signal.getReceiverId())
                    .orElseThrow(() -> new ResourceNotFoundException("Receiver", signal.getReceiverId()));

            // Forward WebRTC signal to the other peer
            messagingTemplate.convertAndSendToUser(
                    receiver.getEmail(),
                    "/queue/webrtc-signal",
                    signal
            );

            log.debug("WebRTC signal forwarded from {} to {}", userEmail, receiver.getEmail());
        } catch (Exception e) {
            log.error("Error handling WebRTC signal", e);
        }
    }
}

class TypingNotification {
    @JsonProperty("senderId")
    private String senderId;
    
    @JsonProperty("senderName")
    private String senderName;
    
    @JsonProperty("receiverId")
    private String receiverId;
    
    @JsonProperty("isTyping")
    private boolean typing;

    @JsonProperty("senderId")
    public String getSenderId() {
        return senderId;
    }

    @JsonProperty("senderId")
    public void setSenderId(String senderId) {
        this.senderId = senderId;
    }

    @JsonProperty("receiverId")
    public String getReceiverId() {
        return receiverId;
    }

    @JsonProperty("receiverId")
    public void setReceiverId(String receiverId) {
        this.receiverId = receiverId;
    }

    @JsonProperty("isTyping")
    public boolean isTyping() {
        return typing;
    }

    @JsonProperty("isTyping")
    public void setTyping(boolean typing) {
        this.typing = typing;
    }

    @JsonProperty("senderName")
    public String getSenderName() {
        return senderName;
    }

    @JsonProperty("senderName")
    public void setSenderName(String senderName) {
        this.senderName = senderName;
    }
}

