package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.prescription.PrescriptionRequestDto;
import com.gnx.telemedicine.dto.prescription.PrescriptionResponseDto;
import com.gnx.telemedicine.mappers.PrescriptionMapper;
import com.gnx.telemedicine.model.Appointment;
import com.gnx.telemedicine.model.Prescription;
import com.gnx.telemedicine.model.enums.PrescriptionStatus;
import com.gnx.telemedicine.repository.AppointmentRepository;
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.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class PrescriptionService {
    
    private final PrescriptionRepository prescriptionRepository;
    private final PrescriptionMapper prescriptionMapper;
    private final PatientRepository patientRepository;
    private final DoctorRepository doctorRepository;
    private final AppointmentRepository appointmentRepository;
    private final UserRepository userRepository;
    private final ClinicalAlertService clinicalAlertService;

    @Transactional(readOnly = true)
    public List<PrescriptionResponseDto> getPrescriptionsByPatientId(UUID patientId) {
        return prescriptionRepository.findByPatientIdOrderByCreatedAtDesc(patientId)
                .stream()
                .map(prescriptionMapper::toPrescriptionResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<PrescriptionResponseDto> getPrescriptionsByPatientIdAndStatus(UUID patientId, PrescriptionStatus status) {
        return prescriptionRepository.findByPatientIdAndStatusOrderByCreatedAtDesc(patientId, status)
                .stream()
                .map(prescriptionMapper::toPrescriptionResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<PrescriptionResponseDto> getActivePrescriptionsByPatientId(UUID patientId) {
        return prescriptionRepository.findActivePrescriptionsByPatientId(patientId, LocalDate.now())
                .stream()
                .map(prescriptionMapper::toPrescriptionResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<PrescriptionResponseDto> getPrescriptionsByDoctorId(UUID doctorId) {
        return prescriptionRepository.findByDoctorIdOrderByCreatedAtDesc(doctorId)
                .stream()
                .map(prescriptionMapper::toPrescriptionResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<PrescriptionResponseDto> getPrescriptionsByAppointmentId(UUID appointmentId) {
        return prescriptionRepository.findByAppointmentId(appointmentId)
                .stream()
                .map(prescriptionMapper::toPrescriptionResponseDto)
                .toList();
    }

    @Transactional(rollbackFor = {Exception.class})
    public PrescriptionResponseDto createPrescription(String userEmail, PrescriptionRequestDto requestDto) {
        // Get current user (creator)
        var currentUser = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));

        // Validate user is a doctor
        if (!currentUser.getRole().name().equals("DOCTOR")) {
            throw new IllegalArgumentException("Only doctors can create prescriptions");
        }

        // Get the doctor profile for current user
        var currentDoctor = doctorRepository.findByUser(currentUser)
                .orElseThrow(() -> new IllegalArgumentException("Doctor profile not found for current user"));

        // Validate and get patient
        var patient = patientRepository.findById(requestDto.patientId())
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));

        // Validate that the doctor in request matches the authenticated doctor
        // Doctors can only create prescriptions for themselves
        if (!currentDoctor.getId().equals(requestDto.doctorId())) {
            throw new IllegalArgumentException("Doctors can only create prescriptions for themselves");
        }

        var doctor = currentDoctor;

        // Validate appointment if provided
        Appointment appointment = null;
        if (requestDto.appointmentId() != null) {
            appointment = appointmentRepository.findById(requestDto.appointmentId())
                    .orElseThrow(() -> new IllegalArgumentException("Appointment not found"));
        }

        // Create prescription
        Prescription prescription = prescriptionMapper.toPrescription(requestDto);
        // Don't set ID manually - let Hibernate generate it via @GeneratedValue
        prescription.setPatient(patient);
        prescription.setDoctor(doctor);
        prescription.setAppointment(appointment);
        prescription.setCreatedBy(currentUser);
        
        // Generate unique prescription number
        prescription.setPrescriptionNumber(generatePrescriptionNumber());
        
        // Set default values
        if (prescription.getRefills() == null) {
            prescription.setRefills(0);
        }
        if (prescription.getStatus() == null) {
            prescription.setStatus(PrescriptionStatus.ACTIVE);
        }
        if (prescription.getEPrescriptionSent() == null) {
            prescription.setEPrescriptionSent(false);
        }

        Prescription saved = prescriptionRepository.save(prescription);
        
        // Automatically check for safety issues after prescription creation
        try {
            // Check for drug interactions with existing active prescriptions
            clinicalAlertService.checkForDrugInteractions(patient.getId());
            
            // Check for allergy conflicts with the new medication
            if (requestDto.medicationName() != null && !requestDto.medicationName().trim().isEmpty()) {
                clinicalAlertService.checkForAllergyConflicts(patient.getId(), requestDto.medicationName());
            }
        } catch (Exception e) {
            // Log error but don't fail prescription creation if safety check fails
            // This ensures prescription creation succeeds even if safety checks have issues
            log.warn("Warning: Safety check failed for prescription: {}", e.getMessage(), e);
        }
        
        return prescriptionMapper.toPrescriptionResponseDto(saved);
    }

    @Transactional(readOnly = true)
    public PrescriptionResponseDto getPrescriptionById(UUID id) {
        Prescription prescription = prescriptionRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Prescription not found"));
        return prescriptionMapper.toPrescriptionResponseDto(prescription);
    }

    @Transactional(readOnly = true)
    public List<PrescriptionResponseDto> getPrescriptionsByPatientIdAndDoctorId(UUID patientId, UUID doctorId) {
        return prescriptionRepository.findByPatientIdAndDoctorId(patientId, doctorId)
                .stream()
                .map(prescriptionMapper::toPrescriptionResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public PrescriptionResponseDto getPrescriptionByNumber(String prescriptionNumber) {
        Prescription prescription = prescriptionRepository.findByPrescriptionNumber(prescriptionNumber)
                .orElseThrow(() -> new IllegalArgumentException("Prescription not found"));
        return prescriptionMapper.toPrescriptionResponseDto(prescription);
    }

    @Transactional(rollbackFor = {Exception.class})
    public PrescriptionResponseDto updatePrescriptionStatus(String userEmail, UUID id, PrescriptionStatus status) {
        Prescription prescription = prescriptionRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Prescription not found"));
        
        prescription.setStatus(status);
        
        // If discontinuing, set end date to today if not already set
        if (status == PrescriptionStatus.DISCONTINUED && prescription.getEndDate() == null) {
            prescription.setEndDate(LocalDate.now());
        }
        
        Prescription saved = prescriptionRepository.save(prescription);
        return prescriptionMapper.toPrescriptionResponseDto(saved);
    }

    @Transactional
    public PrescriptionResponseDto markEPrescriptionSent(String userEmail, UUID id) {
        Prescription prescription = prescriptionRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Prescription not found"));
        
        prescription.setEPrescriptionSent(true);
        prescription.setEPrescriptionSentAt(Instant.now());
        
        Prescription saved = prescriptionRepository.save(prescription);
        return prescriptionMapper.toPrescriptionResponseDto(saved);
    }

    @Transactional(rollbackFor = {Exception.class})
    public PrescriptionResponseDto updatePrescription(String userEmail, UUID id, PrescriptionRequestDto requestDto) {
        Prescription prescription = prescriptionRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Prescription not found"));

        // Update fields
        prescription.setMedicationName(requestDto.medicationName());
        prescription.setMedicationCode(requestDto.medicationCode());
        prescription.setDosage(requestDto.dosage());
        prescription.setFrequency(requestDto.frequency());
        prescription.setQuantity(requestDto.quantity());
        if (requestDto.refills() != null) {
            prescription.setRefills(requestDto.refills());
        }
        prescription.setInstructions(requestDto.instructions());
        prescription.setStartDate(requestDto.startDate());
        prescription.setEndDate(requestDto.endDate());
        if (requestDto.status() != null) {
            prescription.setStatus(requestDto.status());
        }
        prescription.setPharmacyName(requestDto.pharmacyName());
        prescription.setPharmacyAddress(requestDto.pharmacyAddress());
        prescription.setPharmacyPhone(requestDto.pharmacyPhone());

        Prescription saved = prescriptionRepository.save(prescription);
        
        // Automatically check for safety issues after prescription update
        // Check if medication name changed or status is ACTIVE
        String oldMedicationName = prescription.getMedicationName();
        String newMedicationName = requestDto.medicationName();
        boolean medicationChanged = newMedicationName != null && 
                                    !newMedicationName.trim().isEmpty() && 
                                    !newMedicationName.equals(oldMedicationName);
        boolean isActive = requestDto.status() == null || requestDto.status() == PrescriptionStatus.ACTIVE;
        
        if (isActive && (medicationChanged || newMedicationName != null)) {
            try {
                // Check for drug interactions with existing active prescriptions
                clinicalAlertService.checkForDrugInteractions(saved.getPatient().getId());
                
                // Check for allergy conflicts if medication name was provided
                if (newMedicationName != null && !newMedicationName.trim().isEmpty()) {
                    clinicalAlertService.checkForAllergyConflicts(saved.getPatient().getId(), newMedicationName);
                }
            } catch (Exception e) {
                // Log error but don't fail prescription update if safety check fails
                log.warn("Warning: Safety check failed for prescription update: {}", e.getMessage(), e);
            }
        }
        
        return prescriptionMapper.toPrescriptionResponseDto(saved);
    }

    @Transactional(rollbackFor = {Exception.class})
    public void deletePrescription(UUID id) {
        prescriptionRepository.deleteById(id);
    }

    private String generatePrescriptionNumber() {
        // Generate unique prescription number: PRE + timestamp + random
        return "PRE-" + System.currentTimeMillis() + "-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }
}

