package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.medical.MedicalRecordRequestDto;
import com.gnx.telemedicine.dto.medical.MedicalRecordResponseDto;
import com.gnx.telemedicine.mappers.MedicalRecordMapper;
import com.gnx.telemedicine.model.Appointment;
import com.gnx.telemedicine.model.MedicalRecord;
import com.gnx.telemedicine.model.enums.RecordType;
import com.gnx.telemedicine.repository.AppointmentRepository;
import com.gnx.telemedicine.repository.DoctorRepository;
import com.gnx.telemedicine.repository.MedicalRecordRepository;
import com.gnx.telemedicine.repository.PatientRepository;
import com.gnx.telemedicine.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
public class MedicalRecordService {
    
    private final MedicalRecordRepository medicalRecordRepository;
    private final MedicalRecordMapper medicalRecordMapper;
    private final PatientRepository patientRepository;
    private final DoctorRepository doctorRepository;
    private final AppointmentRepository appointmentRepository;
    private final UserRepository userRepository;

    @Transactional(readOnly = true)
    public List<MedicalRecordResponseDto> getMedicalRecordsByPatientId(UUID patientId) {
        return medicalRecordRepository.findByPatientIdOrderByCreatedAtDesc(patientId)
                .stream()
                .map(medicalRecordMapper::toMedicalRecordResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<MedicalRecordResponseDto> getMedicalRecordsByPatientIdAndType(UUID patientId, RecordType recordType) {
        return medicalRecordRepository.findByPatientIdAndRecordTypeOrderByCreatedAtDesc(patientId, recordType)
                .stream()
                .map(medicalRecordMapper::toMedicalRecordResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<MedicalRecordResponseDto> getMedicalRecordsByDoctorId(UUID doctorId) {
        return medicalRecordRepository.findByDoctorIdOrderByCreatedAtDesc(doctorId)
                .stream()
                .map(medicalRecordMapper::toMedicalRecordResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<MedicalRecordResponseDto> getMedicalRecordsByAppointmentId(UUID appointmentId) {
        return medicalRecordRepository.findByAppointmentId(appointmentId)
                .stream()
                .map(medicalRecordMapper::toMedicalRecordResponseDto)
                .toList();
    }

    @Transactional
    public MedicalRecordResponseDto createMedicalRecord(String userEmail, MedicalRecordRequestDto 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 medical records");
        }

        // 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 records for themselves
        if (!currentDoctor.getId().equals(requestDto.doctorId())) {
            throw new IllegalArgumentException("Doctors can only create medical records 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 medical record
        MedicalRecord medicalRecord = medicalRecordMapper.toMedicalRecord(requestDto);
        // Don't set ID manually - let Hibernate generate it via @GeneratedValue
        medicalRecord.setPatient(patient);
        medicalRecord.setDoctor(doctor);
        medicalRecord.setAppointment(appointment);
        medicalRecord.setCreatedBy(currentUser);
        medicalRecord.setCreatedAt(Instant.now());
        medicalRecord.setUpdatedAt(Instant.now());

        MedicalRecord saved = medicalRecordRepository.save(medicalRecord);
        return medicalRecordMapper.toMedicalRecordResponseDto(saved);
    }

    @Transactional(readOnly = true)
    public MedicalRecordResponseDto getMedicalRecordById(UUID id) {
        MedicalRecord medicalRecord = medicalRecordRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Medical record not found"));
        
        // Log PHI access (if aspect doesn't catch it)
        // The aspect should handle this, but we'll also log at service level as backup
        
        return medicalRecordMapper.toMedicalRecordResponseDto(medicalRecord);
    }

    @Transactional(readOnly = true)
    public List<MedicalRecordResponseDto> getMedicalRecordsByPatientIdAndDoctorId(UUID patientId, UUID doctorId) {
        return medicalRecordRepository.findByPatientIdAndDoctorId(patientId, doctorId)
                .stream()
                .map(medicalRecordMapper::toMedicalRecordResponseDto)
                .toList();
    }

    @Transactional
    public MedicalRecordResponseDto updateMedicalRecord(String userEmail, UUID id, MedicalRecordRequestDto requestDto) {
        MedicalRecord medicalRecord = medicalRecordRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Medical record not found"));

        // Update fields
        medicalRecord.setTitle(requestDto.title());
        medicalRecord.setContent(requestDto.content());
        medicalRecord.setRecordType(requestDto.recordType());
        medicalRecord.setDiagnosisCode(requestDto.diagnosisCode());
        medicalRecord.setUpdatedAt(Instant.now());

        // Update patient/doctor/appointment if changed
        if (!medicalRecord.getPatient().getId().equals(requestDto.patientId())) {
            var patient = patientRepository.findById(requestDto.patientId())
                    .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
            medicalRecord.setPatient(patient);
        }

        if (!medicalRecord.getDoctor().getId().equals(requestDto.doctorId())) {
            var doctor = doctorRepository.findById(requestDto.doctorId())
                    .orElseThrow(() -> new IllegalArgumentException("Doctor not found"));
            medicalRecord.setDoctor(doctor);
        }

        if (requestDto.appointmentId() != null && 
            (medicalRecord.getAppointment() == null || !medicalRecord.getAppointment().getId().equals(requestDto.appointmentId()))) {
            var appointment = appointmentRepository.findById(requestDto.appointmentId())
                    .orElseThrow(() -> new IllegalArgumentException("Appointment not found"));
            medicalRecord.setAppointment(appointment);
        }

        MedicalRecord saved = medicalRecordRepository.save(medicalRecord);
        return medicalRecordMapper.toMedicalRecordResponseDto(saved);
    }

    @Transactional
    public void deleteMedicalRecord(UUID id) {
        medicalRecordRepository.deleteById(id);
    }
}

