package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.patient_safety.DuplicatePatientRecordResponseDto;
import com.gnx.telemedicine.dto.patient_safety.DuplicatePatientReviewRequestDto;
import com.gnx.telemedicine.mappers.DuplicatePatientRecordMapper;
import com.gnx.telemedicine.model.DuplicateMatchReason;
import com.gnx.telemedicine.model.DuplicatePatientRecord;
import com.gnx.telemedicine.model.Patient;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.model.enums.DuplicateStatus;
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 java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
@Slf4j
public class DuplicatePatientDetectionService {
    
    private final DuplicatePatientRecordRepository duplicatePatientRecordRepository;
    private final DuplicatePatientRecordMapper duplicatePatientRecordMapper;
    private final PatientRepository patientRepository;
    private final UserRepository userRepository;
    
    @Transactional(readOnly = true)
    public List<DuplicatePatientRecordResponseDto> getAllDuplicateRecords() {
        return duplicatePatientRecordRepository.findAll()
                .stream()
                .map(duplicatePatientRecordMapper::toDuplicatePatientRecordResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<DuplicatePatientRecordResponseDto> getDuplicateRecordsByStatus(DuplicateStatus status) {
        return duplicatePatientRecordRepository.findByStatusOrderByCreatedAtDesc(status)
                .stream()
                .map(duplicatePatientRecordMapper::toDuplicatePatientRecordResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<DuplicatePatientRecordResponseDto> getPendingDuplicateRecords() {
        return duplicatePatientRecordRepository.findAllPendingOrderedByMatchScore()
                .stream()
                .map(duplicatePatientRecordMapper::toDuplicatePatientRecordResponseDto)
                .toList();
    }
    
    @Transactional(readOnly = true)
    public List<DuplicatePatientRecordResponseDto> getDuplicatesForPatient(UUID patientId) {
        Patient patient = patientRepository.findById(patientId)
                .orElseThrow(() -> new IllegalArgumentException("Patient not found"));
        
        List<DuplicatePatientRecordResponseDto> results = new ArrayList<>();
        
        // Check if patient is primary
        results.addAll(duplicatePatientRecordRepository.findByPrimaryPatientOrderByCreatedAtDesc(patient)
                .stream()
                .map(duplicatePatientRecordMapper::toDuplicatePatientRecordResponseDto)
                .toList());
        
        // Check if patient is duplicate
        results.addAll(duplicatePatientRecordRepository.findByDuplicatePatientOrderByCreatedAtDesc(patient)
                .stream()
                .map(duplicatePatientRecordMapper::toDuplicatePatientRecordResponseDto)
                .toList());
        
        return results;
    }
    
    @Transactional
    public DuplicatePatientRecordResponseDto reviewDuplicate(String userEmail, UUID duplicateId, DuplicatePatientReviewRequestDto requestDto) {
        DuplicatePatientRecord record = duplicatePatientRecordRepository.findById(duplicateId)
                .orElseThrow(() -> new IllegalArgumentException("Duplicate record not found"));
        
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new RuntimeException("User not found"));
        
        record.setStatus(requestDto.status());
        record.setReviewNotes(requestDto.reviewNotes());
        record.setReviewedBy(user);
        record.setReviewedAt(java.time.Instant.now());
        
        DuplicatePatientRecord saved = duplicatePatientRecordRepository.save(record);
        log.info("Duplicate record {} reviewed by user {} with status {}", duplicateId, userEmail, requestDto.status());
        return duplicatePatientRecordMapper.toDuplicatePatientRecordResponseDto(saved);
    }
    
    @Transactional(readOnly = true)
    public DuplicatePatientRecordResponseDto getDuplicateRecordById(UUID id) {
        DuplicatePatientRecord record = duplicatePatientRecordRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Duplicate record not found"));
        return duplicatePatientRecordMapper.toDuplicatePatientRecordResponseDto(record);
    }
    
    public long countPendingDuplicates() {
        return duplicatePatientRecordRepository.countPendingDuplicates();
    }
    
    @Transactional
    public void scanForDuplicates() {
        log.info("Starting duplicate patient detection scan");
        List<Patient> allPatients = patientRepository.findAll();
        int duplicateCount = 0;
        
        for (int i = 0; i < allPatients.size(); i++) {
            for (int j = i + 1; j < allPatients.size(); j++) {
                Patient p1 = allPatients.get(i);
                Patient p2 = allPatients.get(j);
                
                BigDecimal matchScore = calculateMatchScore(p1, p2);
                
                // If match score is above threshold, create duplicate record
                if (matchScore.compareTo(new BigDecimal("70.0")) >= 0) {
                    createDuplicateRecordIfNotExists(p1, p2, matchScore);
                    duplicateCount++;
                }
            }
        }
        
        log.info("Duplicate scan completed. Found {} potential duplicates", duplicateCount);
    }
    
    private BigDecimal calculateMatchScore(Patient p1, Patient p2) {
        UserModel u1 = p1.getUser();
        UserModel u2 = p2.getUser();
        
        BigDecimal score = BigDecimal.ZERO;
        List<String> reasons = new ArrayList<>();
        
        // Name match
        if (u1.getFirstName().equalsIgnoreCase(u2.getFirstName())) {
            score = score.add(new BigDecimal("20"));
            reasons.add("Matching first name");
        }
        if (u1.getLastName().equalsIgnoreCase(u2.getLastName())) {
            score = score.add(new BigDecimal("20"));
            reasons.add("Matching last name");
        }
        
        // Phone match
        if (u1.getPhoneNumber() != null && u2.getPhoneNumber() != null &&
            u1.getPhoneNumber().equals(u2.getPhoneNumber())) {
            score = score.add(new BigDecimal("30"));
            reasons.add("Matching phone number");
        }
        
        // Blood type match
        if (p1.getBloodType() != null && p2.getBloodType() != null &&
            p1.getBloodType().equals(p2.getBloodType())) {
            score = score.add(new BigDecimal("15"));
            reasons.add("Matching blood type");
        }
        
        // Allergy match
        if (p1.getAllergies() != null && p2.getAllergies() != null &&
            !p1.getAllergies().isEmpty() && !p2.getAllergies().isEmpty()) {
            List<String> commonAllergies = new ArrayList<>(p1.getAllergies());
            commonAllergies.retainAll(p2.getAllergies());
            if (!commonAllergies.isEmpty()) {
                score = score.add(new BigDecimal("15"));
                reasons.add("Common allergies: " + String.join(", ", commonAllergies));
            }
        }
        
        return score;
    }
    
    private void createDuplicateRecordIfNotExists(Patient p1, Patient p2, BigDecimal matchScore) {
        // Check if record already exists
        if (duplicatePatientRecordRepository.findByPrimaryPatientAndDuplicatePatient(p1, p2).isPresent() ||
            duplicatePatientRecordRepository.findByPrimaryPatientAndDuplicatePatient(p2, p1).isPresent()) {
            return; // Already exists
        }
        
        DuplicatePatientRecord record = new DuplicatePatientRecord();
        record.setPrimaryPatient(p1);
        record.setDuplicatePatient(p2);
        record.setMatchScore(matchScore);
        record.setStatus(DuplicateStatus.PENDING);
        
        List<DuplicateMatchReason> reasons = new ArrayList<>();
        if (p1.getUser().getFirstName().equalsIgnoreCase(p2.getUser().getFirstName())) {
            DuplicateMatchReason reason = new DuplicateMatchReason();
            reason.setDuplicateRecord(record);
            reason.setReason("Matching first name");
            reasons.add(reason);
        }
        if (p1.getUser().getLastName().equalsIgnoreCase(p2.getUser().getLastName())) {
            DuplicateMatchReason reason = new DuplicateMatchReason();
            reason.setDuplicateRecord(record);
            reason.setReason("Matching last name");
            reasons.add(reason);
        }
        if (p1.getUser().getPhoneNumber() != null && p1.getUser().getPhoneNumber().equals(p2.getUser().getPhoneNumber())) {
            DuplicateMatchReason reason = new DuplicateMatchReason();
            reason.setDuplicateRecord(record);
            reason.setReason("Matching phone");
            reasons.add(reason);
        }
        record.setMatchReasons(reasons);
        
        duplicatePatientRecordRepository.save(record);
        log.info("Created duplicate record for patients {} and {} with match score {}", 
                p1.getId(), p2.getId(), matchScore);
    }
}

