package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.patient_safety.CriticalResultAcknowledgmentRequestDto;
import com.gnx.telemedicine.dto.patient_safety.CriticalResultResponseDto;
import com.gnx.telemedicine.mappers.CriticalResultMapper;
import com.gnx.telemedicine.model.*;
import com.gnx.telemedicine.model.enums.CriticalityLevel;
import com.gnx.telemedicine.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.gnx.telemedicine.model.Appointment;
import com.gnx.telemedicine.model.Prescription;
import com.gnx.telemedicine.repository.AppointmentRepository;
import com.gnx.telemedicine.repository.PrescriptionRepository;

import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class CriticalResultManagementService {
    
    private final CriticalResultRepository criticalResultRepository;
    private final CriticalResultMapper criticalResultMapper;
    private final LabResultRepository labResultRepository;
    private final PatientRepository patientRepository;
    private final DoctorRepository doctorRepository;
    private final UserRepository userRepository;
    private final EmailNotificationService emailNotificationService;
    private final AppointmentRepository appointmentRepository;
    private final PrescriptionRepository prescriptionRepository;
    
    @Transactional(readOnly = true)
    public List<CriticalResultResponseDto> getCriticalResultsByPatientId(UUID patientId) {
        Patient patient = patientRepository.findById(patientId)
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
        return criticalResultRepository.findByPatientOrderByCreatedAtDesc(patient)
                .stream()
                .map(criticalResultMapper::toCriticalResultResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<CriticalResultResponseDto> getUnacknowledgedCriticalResultsByPatientId(UUID patientId) {
        return criticalResultRepository.findUnacknowledgedCriticalResultsByPatientId(patientId)
                .stream()
                .map(criticalResultMapper::toCriticalResultResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<CriticalResultResponseDto> getUnacknowledgedCriticalResultsByDoctorId(UUID doctorId) {
        return criticalResultRepository.findUnacknowledgedCriticalResultsByDoctorId(doctorId)
                .stream()
                .map(criticalResultMapper::toCriticalResultResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<CriticalResultResponseDto> getAllUnacknowledgedCriticalResults() {
        return criticalResultRepository.findByAcknowledgedFalseOrderByCriticalityLevelDescCreatedAtDesc()
                .stream()
                .map(criticalResultMapper::toCriticalResultResponseDto)
                .toList();
    }
    
    @Transactional
    public CriticalResultResponseDto createCriticalResult(UUID labResultId, CriticalityLevel criticalityLevel, String clinicalSignificance) {
        LabResult labResult = labResultRepository.findById(labResultId)
                .orElseThrow(() -> new IllegalArgumentException("Lab result not found"));
        
        Patient patient = labResult.getPatient();
        
        // Find the treating doctor for this patient
        // Try to get from most recent appointment or prescription
        Doctor doctor = findTreatingDoctorForPatient(patient.getId());
        if (doctor == null) {
            throw new IllegalArgumentException("No treating doctor found for patient. Cannot create critical result without a treating doctor.");
        }
        
        CriticalResult criticalResult = new CriticalResult();
        criticalResult.setLabResult(labResult);
        criticalResult.setPatient(patient);
        criticalResult.setDoctor(doctor);
        criticalResult.setCriticalityLevel(criticalityLevel);
        criticalResult.setTestName(labResult.getTestName());
        criticalResult.setResultValue(labResult.getResultValue());
        criticalResult.setReferenceRange(labResult.getReferenceRange());
        criticalResult.setClinicalSignificance(clinicalSignificance);
        criticalResult.setAcknowledgmentRequired(true);
        criticalResult.setFollowUpRequired(true);
        criticalResult.setNotifiedAt(Instant.now());
        
        CriticalResult saved = criticalResultRepository.save(criticalResult);
        log.info("Created critical result {} for lab result {}", saved.getId(), labResultId);
        
        // Send notification to doctor
        sendCriticalResultNotification(saved, doctor);
        
        return criticalResultMapper.toCriticalResultResponseDto(saved);
    }
    
    @Transactional
    public CriticalResultResponseDto acknowledgeCriticalResult(String userEmail, UUID resultId, CriticalResultAcknowledgmentRequestDto requestDto) {
        CriticalResult criticalResult = criticalResultRepository.findById(resultId)
                .orElseThrow(() -> new IllegalArgumentException("Critical result not found"));
        
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));
        
        criticalResult.setAcknowledged(true);
        criticalResult.setAcknowledgedAt(Instant.now());
        criticalResult.setAcknowledgedBy(user);
        
        if (requestDto.acknowledgmentMethod() != null) {
            criticalResult.setAcknowledgmentMethod(requestDto.acknowledgmentMethod());
        }
        
        if (requestDto.followUpStatus() != null) {
            criticalResult.setFollowUpStatus(requestDto.followUpStatus());
        }
        
        CriticalResult saved = criticalResultRepository.save(criticalResult);
        log.info("Critical result {} acknowledged by user {}", resultId, userEmail);
        return criticalResultMapper.toCriticalResultResponseDto(saved);
    }
    
    @Transactional(readOnly = true)
    public CriticalResultResponseDto getCriticalResultById(UUID id) {
        CriticalResult result = criticalResultRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Critical result not found"));
        return criticalResultMapper.toCriticalResultResponseDto(result);
    }
    
    public long countUnacknowledgedByPatientId(UUID patientId) {
        return criticalResultRepository.countByPatientAndAcknowledgedFalse(
                patientRepository.findById(patientId)
                        .orElseThrow(() -> new IllegalArgumentException("Patient not found"))
        );
    }
    
    public long countUnacknowledgedByDoctorId(UUID doctorId) {
        return criticalResultRepository.countByDoctorAndAcknowledgedFalse(
                doctorRepository.findById(doctorId)
                        .orElseThrow(() -> new IllegalArgumentException("Doctor not found"))
        );
    }
    
    private void sendCriticalResultNotification(CriticalResult criticalResult, Doctor doctor) {
        try {
            String subject = "CRITICAL LAB RESULT ALERT";
            String message = String.format(
                    "CRITICAL LAB RESULT ALERT\n\n" +
                    "Patient: %s %s\n" +
                    "Test: %s\n" +
                    "Result: %s %s\n" +
                    "Criticality Level: %s\n" +
                    "Clinical Significance: %s\n" +
                    "\nImmediate attention required!",
                    criticalResult.getPatient().getUser().getFirstName(),
                    criticalResult.getPatient().getUser().getLastName(),
                    criticalResult.getTestName(),
                    criticalResult.getResultValue(),
                    criticalResult.getReferenceRange(),
                    criticalResult.getCriticalityLevel(),
                    criticalResult.getClinicalSignificance()
            );
            
            emailNotificationService.sendEmail(doctor.getUser().getEmail(), subject, message);
            log.info("Critical result notification sent to doctor {}", doctor.getUser().getEmail());
        } catch (Exception e) {
            log.error("Failed to send critical result notification", e);
        }
    }
    
    /**
     * Find the treating doctor for a patient.
     * Priority: 1. Most recent appointment with CONFIRMED or COMPLETED status
     *           2. Most recent prescription's doctor
     *           3. Most recent appointment with any status
     */
    private Doctor findTreatingDoctorForPatient(UUID patientId) {
        // Try to find from most recent appointment with CONFIRMED or COMPLETED status
        List<Appointment> recentAppointments = appointmentRepository.findByPatientId(patientId);
        Optional<Appointment> recentConfirmed = recentAppointments.stream()
                .filter(a -> a.getStatus() != null && 
                           (a.getStatus().name().equals("CONFIRMED") || 
                            a.getStatus().name().equals("COMPLETED")))
                .sorted((a1, a2) -> a2.getCreatedAt().compareTo(a1.getCreatedAt()))
                .findFirst();
        
        if (recentConfirmed.isPresent() && recentConfirmed.get().getDoctor() != null) {
            return recentConfirmed.get().getDoctor();
        }
        
        // Try to find from most recent prescription
        List<Prescription> prescriptions = prescriptionRepository.findByPatientIdOrderByCreatedAtDesc(patientId);
        Optional<Prescription> recentPrescription = prescriptions.stream()
                .filter(p -> p.getDoctor() != null)
                .findFirst();
        
        if (recentPrescription.isPresent()) {
            return recentPrescription.get().getDoctor();
        }
        
        // Fallback: any recent appointment
        Optional<Appointment> anyRecent = recentAppointments.stream()
                .filter(a -> a.getDoctor() != null)
                .sorted((a1, a2) -> a2.getCreatedAt().compareTo(a1.getCreatedAt()))
                .findFirst();
        
        if (anyRecent.isPresent()) {
            return anyRecent.get().getDoctor();
        }
        
        return null;
    }
}

