package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.medical.LabResultRequestDto;
import com.gnx.telemedicine.dto.medical.LabResultResponseDto;
import com.gnx.telemedicine.mappers.LabResultMapper;
import com.gnx.telemedicine.model.LabResult;
import com.gnx.telemedicine.model.MedicalRecord;
import com.gnx.telemedicine.model.enums.LabResultStatus;
import com.gnx.telemedicine.repository.LabResultRepository;
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 LabResultService {
    
    private final LabResultRepository labResultRepository;
    private final LabResultMapper labResultMapper;
    private final MedicalRecordRepository medicalRecordRepository;
    private final PatientRepository patientRepository;
    private final UserRepository userRepository;

    @Transactional(readOnly = true)
    public List<LabResultResponseDto> getLabResultsByPatientId(UUID patientId) {
        return labResultRepository.findByPatientIdOrderByCreatedAtDesc(patientId)
                .stream()
                .map(labResultMapper::toLabResultResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<LabResultResponseDto> getLabResultsByPatientIdAndStatus(UUID patientId, LabResultStatus status) {
        return labResultRepository.findByPatientIdAndStatusOrderByCreatedAtDesc(patientId, status)
                .stream()
                .map(labResultMapper::toLabResultResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<LabResultResponseDto> getAbnormalLabResultsByPatientId(UUID patientId) {
        return labResultRepository.findAbnormalResultsByPatientId(patientId)
                .stream()
                .map(labResultMapper::toLabResultResponseDto)
                .toList();
    }

    @Transactional(readOnly = true)
    public List<LabResultResponseDto> getLabResultsByMedicalRecordId(UUID medicalRecordId) {
        return labResultRepository.findByMedicalRecordId(medicalRecordId)
                .stream()
                .map(labResultMapper::toLabResultResponseDto)
                .toList();
    }

    @Transactional
    public LabResultResponseDto createLabResult(String userEmail, LabResultRequestDto requestDto) {
        // Get current user (orderer)
        var currentUser = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));

        // Validate and get medical record if provided (optional)
        MedicalRecord medicalRecord = null;
        if (requestDto.medicalRecordId() != null) {
            medicalRecord = medicalRecordRepository.findById(requestDto.medicalRecordId())
                    .orElseThrow(() -> new IllegalArgumentException("Medical record not found"));
        }

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

        // Create lab result
        LabResult labResult = labResultMapper.toLabResult(requestDto);
        // Don't set ID manually - let Hibernate generate it via @GeneratedValue
        labResult.setMedicalRecord(medicalRecord); // Can be null
        labResult.setPatient(patient);
        labResult.setOrderedBy(currentUser);
        
        // Set default status if not provided
        if (labResult.getStatus() == null) {
            labResult.setStatus(LabResultStatus.PENDING);
        }

        // Set performed at if not provided
        if (labResult.getPerformedAt() == null) {
            labResult.setPerformedAt(Instant.now());
        }

        LabResult saved = labResultRepository.save(labResult);
        return labResultMapper.toLabResultResponseDto(saved);
    }

    @Transactional(readOnly = true)
    public LabResultResponseDto getLabResultById(UUID id) {
        LabResult labResult = labResultRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Lab result not found"));
        return labResultMapper.toLabResultResponseDto(labResult);
    }

    @Transactional
    public LabResultResponseDto updateLabResult(String userEmail, UUID id, LabResultRequestDto requestDto) {
        LabResult labResult = labResultRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Lab result not found"));

        // Update fields
        labResult.setTestName(requestDto.testName());
        labResult.setTestCode(requestDto.testCode());
        labResult.setResultValue(requestDto.resultValue());
        labResult.setUnit(requestDto.unit());
        labResult.setReferenceRange(requestDto.referenceRange());
        if (requestDto.status() != null) {
            labResult.setStatus(requestDto.status());
        }
        if (requestDto.performedAt() != null) {
            labResult.setPerformedAt(requestDto.performedAt());
        }
        labResult.setResultFileUrl(requestDto.resultFileUrl());

        LabResult saved = labResultRepository.save(labResult);
        return labResultMapper.toLabResultResponseDto(saved);
    }

    @Transactional
    public void deleteLabResult(UUID id) {
        labResultRepository.deleteById(id);
    }
}

