package com.gnx.telemedicine.service;

import com.gnx.telemedicine.model.*;
import com.gnx.telemedicine.model.enums.DataSubjectRequestStatus;
import com.gnx.telemedicine.model.enums.DataSubjectRequestType;
import com.gnx.telemedicine.repository.*;
import com.gnx.telemedicine.service.HipaaAuditService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class GdprDataSubjectRequestService {
    
    private final DataSubjectRequestRepository requestRepository;
    private final UserRepository userRepository;
    private final PatientRepository patientRepository;
    private final AppointmentRepository appointmentRepository;
    private final MedicalRecordRepository medicalRecordRepository;
    private final PrescriptionRepository prescriptionRepository;
    private final HipaaAuditService hipaaAuditService;
    private final EmailNotificationService emailNotificationService;

    @Transactional
    public DataSubjectRequest createRequest(String userEmail, DataSubjectRequestType requestType, 
                                           String description, HttpServletRequest request) {
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        DataSubjectRequest dataSubjectRequest = new DataSubjectRequest();
        dataSubjectRequest.setUser(user);
        dataSubjectRequest.setRequestType(requestType);
        dataSubjectRequest.setRequestStatus(DataSubjectRequestStatus.PENDING);
        dataSubjectRequest.setDescription(description);
        dataSubjectRequest.setRequestedAt(Instant.now());
        
        // Generate verification token for identity verification
        dataSubjectRequest.setVerificationToken(UUID.randomUUID().toString());

        // Log HIPAA audit
        try {
            hipaaAuditService.logAccess(
                    userEmail,
                    com.gnx.telemedicine.model.enums.ActionType.CREATE,
                    "DATA_SUBJECT_REQUEST",
                    dataSubjectRequest.getId(),
                    null,
                    Map.of("requestType", requestType.name(), "description", description != null ? description : ""),
                    request
            );
        } catch (Exception e) {
            log.error("Error logging HIPAA audit for data subject request: {}", e.getMessage());
        }

        return requestRepository.save(dataSubjectRequest);
    }

    @Transactional(readOnly = true)
    public List<DataSubjectRequest> getUserRequests(String userEmail) {
        UserModel user = userRepository.findByEmail(userEmail)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));
        
        return requestRepository.findByUser(user);
    }

    @Transactional
    public DataSubjectRequest processAccessRequest(UUID requestId, String processedByEmail) {
        DataSubjectRequest request = requestRepository.findById(requestId)
                .orElseThrow(() -> new IllegalArgumentException("Request not found"));

        if (request.getRequestType() != DataSubjectRequestType.ACCESS) {
            throw new IllegalArgumentException("Request is not an access request");
        }

        UserModel processedBy = userRepository.findByEmail(processedByEmail)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        // Collect all user data
        Map<String, Object> userData = collectUserData(request.getUser());

        request.setResponseData(userData);
        request.setRequestStatus(DataSubjectRequestStatus.COMPLETED);
        request.setCompletedAt(Instant.now());
        request.setProcessedBy(processedBy);

        // Send notification email
        try {
            emailNotificationService.sendDataSubjectRequestCompletionEmail(
                    request.getUser().getEmail(),
                    request.getRequestType().name()
            );
        } catch (Exception e) {
            log.error("Error sending completion email: {}", e.getMessage());
        }

        return requestRepository.save(request);
    }

    @Transactional
    public DataSubjectRequest processErasureRequest(UUID requestId, String processedByEmail, String notes) {
        DataSubjectRequest request = requestRepository.findById(requestId)
                .orElseThrow(() -> new IllegalArgumentException("Request not found"));

        if (request.getRequestType() != DataSubjectRequestType.ERASURE) {
            throw new IllegalArgumentException("Request is not an erasure request");
        }

        UserModel processedBy = userRepository.findByEmail(processedByEmail)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        // Check for legal retention requirements (HIPAA requires 6 years retention)
        // For erasure requests, we may need to anonymize instead of delete
        // This is a simplified version - in production, you'd need more sophisticated logic
        
        UserModel user = request.getUser();
        
        // Anonymize user data instead of deleting (to comply with HIPAA)
        user.setFirstName("ANONYMIZED");
        user.setLastName("USER");
        user.setEmail("anonymized_" + UUID.randomUUID() + "@deleted.local");
        user.setPhoneNumber(null);
        user.setIsActive(false);
        userRepository.save(user);

        // Anonymize associated patient data if exists
        patientRepository.findByUser(user).ifPresent(patient -> {
            patient.setEmergencyContactName(null);
            patient.setEmergencyContactPhone(null);
            patient.setBloodType(null);
            patient.setAllergies(null);
            patientRepository.save(patient);
        });

        request.setRequestStatus(DataSubjectRequestStatus.COMPLETED);
        request.setCompletedAt(Instant.now());
        request.setProcessedBy(processedBy);
        request.setNotes(notes);

        return requestRepository.save(request);
    }

    @Transactional
    public DataSubjectRequest processPortabilityRequest(UUID requestId, String processedByEmail) {
        DataSubjectRequest request = requestRepository.findById(requestId)
                .orElseThrow(() -> new IllegalArgumentException("Request not found"));

        if (request.getRequestType() != DataSubjectRequestType.PORTABILITY) {
            throw new IllegalArgumentException("Request is not a portability request");
        }

        UserModel processedBy = userRepository.findByEmail(processedByEmail)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        // Collect all user data in a portable format (JSON)
        Map<String, Object> userData = collectUserData(request.getUser());

        request.setResponseData(userData);
        request.setRequestStatus(DataSubjectRequestStatus.COMPLETED);
        request.setCompletedAt(Instant.now());
        request.setProcessedBy(processedBy);

        // Send notification email with data export
        try {
            emailNotificationService.sendDataPortabilityEmail(
                    request.getUser().getEmail(),
                    userData
            );
        } catch (Exception e) {
            log.error("Error sending portability email: {}", e.getMessage());
        }

        return requestRepository.save(request);
    }

    @Transactional
    public DataSubjectRequest rejectRequest(UUID requestId, String processedByEmail, String rejectionReason) {
        DataSubjectRequest request = requestRepository.findById(requestId)
                .orElseThrow(() -> new IllegalArgumentException("Request not found"));

        UserModel processedBy = userRepository.findByEmail(processedByEmail)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        request.setRequestStatus(DataSubjectRequestStatus.REJECTED);
        request.setRejectedAt(Instant.now());
        request.setRejectionReason(rejectionReason);
        request.setProcessedBy(processedBy);

        return requestRepository.save(request);
    }

    @Transactional
    public DataSubjectRequest verifyRequest(String verificationToken) {
        DataSubjectRequest request = requestRepository.findByVerificationToken(verificationToken)
                .orElseThrow(() -> new IllegalArgumentException("Invalid verification token"));

        request.setVerifiedAt(Instant.now());
        return requestRepository.save(request);
    }

    @Transactional(readOnly = true)
    public List<DataSubjectRequest> getPendingRequests() {
        return requestRepository.findByRequestStatus(DataSubjectRequestStatus.PENDING);
    }

    private Map<String, Object> collectUserData(UserModel user) {
        Map<String, Object> data = new HashMap<>();
        
        // Basic user information
        data.put("user", Map.of(
                "id", user.getId().toString(),
                "email", user.getEmail(),
                "firstName", user.getFirstName(),
                "lastName", user.getLastName(),
                "phoneNumber", user.getPhoneNumber() != null ? user.getPhoneNumber() : "",
                "role", user.getRole().name(),
                "createdAt", user.getCreatedAt().toString(),
                "updatedAt", user.getUpdatedAt().toString()
        ));

        // Patient data if exists
        patientRepository.findByUser(user).ifPresent(patient -> {
            Map<String, Object> patientData = new HashMap<>();
            patientData.put("id", patient.getId().toString());
            patientData.put("emergencyContactName", patient.getEmergencyContactName() != null ? patient.getEmergencyContactName() : "");
            patientData.put("emergencyContactPhone", patient.getEmergencyContactPhone() != null ? patient.getEmergencyContactPhone() : "");
            patientData.put("bloodType", patient.getBloodType() != null ? patient.getBloodType() : "");
            patientData.put("allergies", patient.getAllergies() != null ? patient.getAllergies() : List.of());
            patientData.put("createdAt", patient.getCreatedAt().toString());
            data.put("patient", patientData);
        });

        // Appointments - get via patient if exists
        Optional<Patient> patientOpt = patientRepository.findByUser(user);
        if (patientOpt.isPresent()) {
            Patient patient = patientOpt.get();
            List<Map<String, Object>> appointments = appointmentRepository.findByPatientId(patient.getId())
                    .stream()
                    .map(apt -> {
                        Map<String, Object> appointmentData = new HashMap<>();
                        appointmentData.put("id", apt.getId().toString());
                        appointmentData.put("scheduledDate", apt.getScheduledDate() != null ? apt.getScheduledDate().toString() : "");
                        appointmentData.put("scheduledTime", apt.getScheduledTime() != null ? apt.getScheduledTime().toString() : "");
                        appointmentData.put("status", apt.getStatus() != null ? apt.getStatus().name() : "");
                        appointmentData.put("durationMinutes", apt.getDurationMinutes() != null ? apt.getDurationMinutes() : "");
                        appointmentData.put("createdAt", apt.getCreatedAt() != null ? apt.getCreatedAt().toString() : "");
                        return appointmentData;
                    })
                    .collect(Collectors.toList());
            data.put("appointments", appointments);

            // Medical records
            List<Map<String, Object>> medicalRecords = medicalRecordRepository.findByPatientIdOrderByCreatedAtDesc(patient.getId())
                    .stream()
                    .map(mr -> {
                        Map<String, Object> recordData = new HashMap<>();
                        recordData.put("id", mr.getId().toString());
                        recordData.put("title", mr.getTitle() != null ? mr.getTitle() : "");
                        recordData.put("content", mr.getContent() != null ? mr.getContent() : "");
                        recordData.put("diagnosisCode", mr.getDiagnosisCode() != null ? mr.getDiagnosisCode() : "");
                        recordData.put("recordType", mr.getRecordType() != null ? mr.getRecordType().name() : "");
                        recordData.put("createdAt", mr.getCreatedAt() != null ? mr.getCreatedAt().toString() : "");
                        recordData.put("updatedAt", mr.getUpdatedAt() != null ? mr.getUpdatedAt().toString() : "");
                        return recordData;
                    })
                    .collect(Collectors.toList());
            data.put("medicalRecords", medicalRecords);

            // Prescriptions
            List<Map<String, Object>> prescriptions = prescriptionRepository.findByPatientIdOrderByCreatedAtDesc(patient.getId())
                    .stream()
                    .map(p -> {
                        Map<String, Object> prescriptionData = new HashMap<>();
                        prescriptionData.put("id", p.getId().toString());
                        prescriptionData.put("medicationName", p.getMedicationName() != null ? p.getMedicationName() : "");
                        prescriptionData.put("dosage", p.getDosage() != null ? p.getDosage() : "");
                        prescriptionData.put("frequency", p.getFrequency() != null ? p.getFrequency() : "");
                        prescriptionData.put("startDate", p.getStartDate() != null ? p.getStartDate().toString() : "");
                        prescriptionData.put("endDate", p.getEndDate() != null ? p.getEndDate().toString() : "");
                        prescriptionData.put("status", p.getStatus() != null ? p.getStatus().name() : "");
                        prescriptionData.put("createdAt", p.getCreatedAt() != null ? p.getCreatedAt().toString() : "");
                        return prescriptionData;
                    })
                    .collect(Collectors.toList());
            data.put("prescriptions", prescriptions);
        } else {
            data.put("appointments", List.of());
            data.put("medicalRecords", List.of());
            data.put("prescriptions", List.of());
        }

        return data;
    }
}

