package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.patient_safety.ClinicalAlertRequestDto;
import com.gnx.telemedicine.dto.patient_safety.ClinicalAlertResponseDto;
import com.gnx.telemedicine.mappers.ClinicalAlertMapper;
import com.gnx.telemedicine.model.Appointment;
import com.gnx.telemedicine.model.ClinicalAlert;
import com.gnx.telemedicine.model.Doctor;
import com.gnx.telemedicine.model.Patient;
import com.gnx.telemedicine.model.Prescription;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.model.enums.AlertSeverity;
import com.gnx.telemedicine.model.enums.ClinicalAlertType;
import com.gnx.telemedicine.model.enums.PrescriptionStatus;
import com.gnx.telemedicine.repository.AppointmentRepository;
import com.gnx.telemedicine.repository.ClinicalAlertRepository;
import com.gnx.telemedicine.repository.DoctorRepository;
import com.gnx.telemedicine.repository.PatientRepository;
import com.gnx.telemedicine.repository.PrescriptionRepository;
import com.gnx.telemedicine.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
@Slf4j
public class ClinicalAlertService {
    
    private final ClinicalAlertRepository clinicalAlertRepository;
    private final ClinicalAlertMapper clinicalAlertMapper;
    private final PatientRepository patientRepository;
    private final PrescriptionRepository prescriptionRepository;
    private final UserRepository userRepository;
    private final DoctorRepository doctorRepository;
    private final AppointmentRepository appointmentRepository;
    
    // Drug interaction database - simplified for demo
    private static final Set<String> KNOWN_INTERACTIONS = Set.of(
        "WARFARIN-ASPIRIN", "WARFARIN-IBUPROFEN", "WARFARIN-NAPROXEN",
        "DIGOXIN-AMIODARONE", "DIGOXIN-VERAPAMIL",
        "ACE-BETA_BLOCKER", "ACE-POTASSIUM",
        "SSRI-TRAMADOL", "SSRI-MAOI",
        "METFORMIN-CONTRAST_DYE"
    );
    
    @Transactional(readOnly = true)
    public List<ClinicalAlertResponseDto> getAlertsByPatientId(UUID patientId) {
        Patient patient = patientRepository.findById(patientId)
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
        return clinicalAlertRepository.findByPatientOrderByCreatedAtDesc(patient)
                .stream()
                .map(clinicalAlertMapper::toClinicalAlertResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<ClinicalAlertResponseDto> getUnacknowledgedAlertsByPatientId(UUID patientId) {
        return clinicalAlertRepository.findUnacknowledgedAlertsByPatientId(patientId)
                .stream()
                .map(clinicalAlertMapper::toClinicalAlertResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<ClinicalAlertResponseDto> getAllUnacknowledgedAlerts() {
        // This method should only be used by admins or filtered by doctor in controller
        return clinicalAlertRepository.findByAcknowledgedFalseOrderBySeverityDescCreatedAtDesc()
                .stream()
                .map(clinicalAlertMapper::toClinicalAlertResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<ClinicalAlertResponseDto> getUnacknowledgedAlertsByDoctorId(UUID doctorId) {
        return clinicalAlertRepository.findUnacknowledgedAlertsByDoctorId(doctorId)
                .stream()
                .map(clinicalAlertMapper::toClinicalAlertResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<ClinicalAlertResponseDto> getAlertsByDoctorIdAndPatientId(UUID doctorId, UUID patientId) {
        return clinicalAlertRepository.findByDoctorIdAndPatientId(doctorId, patientId)
                .stream()
                .map(clinicalAlertMapper::toClinicalAlertResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<ClinicalAlertResponseDto> getUnacknowledgedAlertsByDoctorIdAndPatientId(UUID doctorId, UUID patientId) {
        return clinicalAlertRepository.findUnacknowledgedAlertsByDoctorIdAndPatientId(doctorId, patientId)
                .stream()
                .map(clinicalAlertMapper::toClinicalAlertResponseDto)
                .toList();
    }
    
    @Transactional
    public ClinicalAlertResponseDto createAlert(String userEmail, ClinicalAlertRequestDto requestDto) {
        Patient patient = patientRepository.findById(requestDto.patientId())
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
        
        ClinicalAlert alert = clinicalAlertMapper.toClinicalAlert(requestDto);
        alert.setPatient(patient);
        
        // Determine the treating doctor
        Doctor treatingDoctor = findTreatingDoctor(patient.getId(), requestDto.relatedPrescriptionId());
        if (treatingDoctor != null) {
            alert.setDoctor(treatingDoctor);
        }
        
        if (requestDto.relatedPrescriptionId() != null) {
            Prescription prescription = prescriptionRepository.findById(requestDto.relatedPrescriptionId())
                    .orElse(null);
            alert.setRelatedPrescription(prescription);
            // If prescription exists and alert doesn't have a doctor, use prescription's doctor
            if (prescription != null && treatingDoctor == null) {
                alert.setDoctor(prescription.getDoctor());
            }
        }
        
        ClinicalAlert saved = clinicalAlertRepository.save(alert);
        log.info("Created clinical alert {} for patient {} assigned to doctor {}", 
                saved.getId(), patient.getId(), 
                saved.getDoctor() != null ? saved.getDoctor().getId() : "none");
        return clinicalAlertMapper.toClinicalAlertResponseDto(saved);
    }
    
    /**
     * 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 findTreatingDoctor(UUID patientId, UUID prescriptionId) {
        // If there's a prescription ID, try to get doctor from that prescription first
        if (prescriptionId != null) {
            Optional<Prescription> prescription = prescriptionRepository.findById(prescriptionId);
            if (prescription.isPresent() && prescription.get().getDoctor() != null) {
                return prescription.get().getDoctor();
            }
        }
        
        // 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().findFirst();
        
        if (recentPrescription.isPresent() && recentPrescription.get().getDoctor() != null) {
            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;
    }
    
    @Transactional
    public ClinicalAlertResponseDto acknowledgeAlert(String userEmail, UUID alertId) {
        ClinicalAlert alert = clinicalAlertRepository.findById(alertId)
                .orElseThrow(() -> new IllegalArgumentException("Alert not found"));
        
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));
        
        alert.setAcknowledged(true);
        alert.setAcknowledgedAt(Instant.now());
        alert.setAcknowledgedBy(user);
        
        ClinicalAlert saved = clinicalAlertRepository.save(alert);
        log.info("Alert {} acknowledged by user {}", alertId, userEmail);
        return clinicalAlertMapper.toClinicalAlertResponseDto(saved);
    }
    
    @Transactional
    public ClinicalAlertResponseDto resolveAlert(String userEmail, UUID alertId) {
        ClinicalAlert alert = clinicalAlertRepository.findById(alertId)
                .orElseThrow(() -> new IllegalArgumentException("Alert not found"));
        
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));
        
        alert.setResolvedAt(Instant.now());
        alert.setResolvedBy(user);
        
        ClinicalAlert saved = clinicalAlertRepository.save(alert);
        log.info("Alert {} resolved by user {}", alertId, userEmail);
        return clinicalAlertMapper.toClinicalAlertResponseDto(saved);
    }
    
    @Transactional
    public void checkForDrugInteractions(UUID patientId) {
        Patient patient = patientRepository.findById(patientId)
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
        
        // Get all active prescriptions for patient
        List<Prescription> activePrescriptions = prescriptionRepository
                .findActivePrescriptionsByPatientId(patientId, LocalDate.now());
        
        if (activePrescriptions.size() < 2) {
            return; // Need at least 2 medications to have interactions
        }
        
        // Check for drug interactions
        for (int i = 0; i < activePrescriptions.size(); i++) {
            for (int j = i + 1; j < activePrescriptions.size(); j++) {
                Prescription p1 = activePrescriptions.get(i);
                Prescription p2 = activePrescriptions.get(j);
                
                String interactionKey1 = p1.getMedicationName().toUpperCase() + "-" + p2.getMedicationName().toUpperCase();
                String interactionKey2 = p2.getMedicationName().toUpperCase() + "-" + p1.getMedicationName().toUpperCase();
                
                if (KNOWN_INTERACTIONS.contains(interactionKey1) || KNOWN_INTERACTIONS.contains(interactionKey2)) {
                    createDrugInteractionAlert(patient, p1, p2);
                }
            }
        }
    }
    
    @Transactional
    public void checkForAllergyConflicts(UUID patientId, String medicationName) {
        Patient patient = patientRepository.findById(patientId)
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
        
        List<String> allergies = patient.getAllergies();
        if (allergies == null || allergies.isEmpty()) {
            return;
        }
        
        for (String allergy : allergies) {
            if (medicationName.toLowerCase().contains(allergy.toLowerCase()) || 
                allergy.toLowerCase().contains(medicationName.toLowerCase())) {
                createAllergyAlert(patient, medicationName, allergy);
            }
        }
    }
    
    private void createDrugInteractionAlert(Patient patient, Prescription p1, Prescription p2) {
        // Check for duplicate unacknowledged alerts to prevent alert fatigue
        List<ClinicalAlert> existingAlerts = clinicalAlertRepository.findDuplicateDrugInteractionAlerts(
                patient.getId(), 
                p1.getMedicationName(), 
                p2.getMedicationName()
        );
        
        if (!existingAlerts.isEmpty()) {
            log.info("Duplicate drug interaction alert already exists for patient {} between {} and {}. Skipping creation.", 
                    patient.getId(), p1.getMedicationName(), p2.getMedicationName());
            return; // Don't create duplicate alert
        }
        
        ClinicalAlert alert = new ClinicalAlert();
        alert.setPatient(patient);
        alert.setAlertType(ClinicalAlertType.DRUG_INTERACTION);
        alert.setSeverity(AlertSeverity.WARNING);
        alert.setTitle("Drug Interaction Detected");
        alert.setDescription(String.format(
                "Potential drug interaction between %s and %s. Please review and consult with physician.",
                p1.getMedicationName(), p2.getMedicationName()
        ));
        alert.setRelatedPrescription(p1);
        
        // Assign to the treating doctor - prefer p1's doctor, fallback to p2's doctor, or find treating doctor
        Doctor doctor = p1.getDoctor();
        if (doctor == null && p2.getDoctor() != null) {
            doctor = p2.getDoctor();
        }
        if (doctor == null) {
            doctor = findTreatingDoctor(patient.getId(), null);
        }
        alert.setDoctor(doctor);
        
        clinicalAlertRepository.save(alert);
        log.info("Created drug interaction alert for patient {} between {} and {} assigned to doctor {}", 
                patient.getId(), p1.getMedicationName(), p2.getMedicationName(),
                doctor != null ? doctor.getId() : "none");
    }
    
    private void createAllergyAlert(Patient patient, String medication, String allergy) {
        // Check for duplicate unacknowledged alerts to prevent alert fatigue
        List<ClinicalAlert> existingAlerts = clinicalAlertRepository.findDuplicateAllergyAlerts(
                patient.getId(), 
                medication
        );
        
        if (!existingAlerts.isEmpty()) {
            log.info("Duplicate allergy alert already exists for patient {} for medication {}. Skipping creation.", 
                    patient.getId(), medication);
            return; // Don't create duplicate alert
        }
        
        ClinicalAlert alert = new ClinicalAlert();
        alert.setPatient(patient);
        alert.setAlertType(ClinicalAlertType.ALLERGY);
        alert.setSeverity(AlertSeverity.CRITICAL);
        alert.setTitle("Allergy Alert");
        alert.setDescription(String.format(
                "Medication %s may contain or interact with known allergy: %s",
                medication, allergy
        ));
        alert.setMedicationName(medication);
        
        // Assign to the treating doctor
        Doctor doctor = findTreatingDoctor(patient.getId(), null);
        alert.setDoctor(doctor);
        
        clinicalAlertRepository.save(alert);
        log.info("Created allergy alert for patient {} for medication {} with allergy {} assigned to doctor {}", 
                patient.getId(), medication, allergy,
                doctor != null ? doctor.getId() : "none");
    }
}

