package com.gnx.telemedicine.service;

import com.gnx.telemedicine.dto.admin.AdminStatsResponseDto;
import com.gnx.telemedicine.dto.admin.MetricsResponseDto;
import com.gnx.telemedicine.dto.admin.PaginatedResponse;
import com.gnx.telemedicine.dto.admin.UserManagementDto;
import com.gnx.telemedicine.dto.appointment.AppointmentResponseDto;
import com.gnx.telemedicine.mappers.UserMapper;
import com.gnx.telemedicine.model.Doctor;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.model.enums.Role;
import com.gnx.telemedicine.repository.AppointmentRepository;
import com.gnx.telemedicine.repository.BreachNotificationRepository;
import com.gnx.telemedicine.repository.DoctorRepository;
import com.gnx.telemedicine.repository.HipaaAuditLogRepository;
import com.gnx.telemedicine.repository.PatientRepository;
import com.gnx.telemedicine.repository.UserRepository;
import com.gnx.telemedicine.service.AppointmentService;
import com.gnx.telemedicine.util.PaginationUtil;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class AdminService {
    private final UserRepository userRepository;
    private final AppointmentRepository appointmentRepository;
    private final AppointmentService appointmentService;

    private final UserMapper userMapper;
    private final DoctorRepository doctorRepository;
    private final MeterRegistry meterRegistry;
    private final PatientRepository patientRepository;
    private final HipaaAuditLogRepository hipaaAuditLogRepository;
    private final BreachNotificationRepository breachNotificationRepository;


    @Transactional(readOnly = true)
    @Cacheable(value = "users")
    public List<UserManagementDto> getAllUsers() {
       return userRepository.findAll().stream()
               .map(user -> {
                   UserManagementDto baseDto = userMapper.toUserManagementDto(user);
                   // For doctor users, add doctor-specific info
                   if (user.getRole() == Role.DOCTOR) {
                       Doctor doctor = doctorRepository.findByUser(user).orElse(null);
                       if (doctor != null) {
                           return new UserManagementDto(
                                   baseDto.id(),
                                   baseDto.email(),
                                   baseDto.firstName(),
                                   baseDto.lastName(),
                                   baseDto.phoneNumber(),
                                   baseDto.role(),
                                   baseDto.isActive(),
                                   baseDto.createdAt(),
                                   baseDto.avatarUrl(),
                                   baseDto.isOnline(),
                                   baseDto.status(),
                                   doctor.getMedicalLicenseNumber(),
                                   doctor.getIsVerified()
                           );
                       }
                   }
                   return baseDto;
               })
               .toList();
    }

    @Transactional(readOnly = true)
    public PaginatedResponse<UserManagementDto> getAllUsersPaginated(Integer page, Integer size, String sortBy, String direction) {
        Pageable pageable = PaginationUtil.createPageable(page, size, sortBy, direction);
        
        Page<UserModel> userPage = userRepository.findAll(pageable);
        
        List<UserManagementDto> users = userPage.getContent().stream()
                .map(user -> {
                    UserManagementDto baseDto = userMapper.toUserManagementDto(user);
                    // For doctor users, add doctor-specific info
                    if (user.getRole() == Role.DOCTOR) {
                        Doctor doctor = doctorRepository.findByUser(user).orElse(null);
                        if (doctor != null) {
                            return new UserManagementDto(
                                    baseDto.id(),
                                    baseDto.email(),
                                    baseDto.firstName(),
                                    baseDto.lastName(),
                                    baseDto.phoneNumber(),
                                    baseDto.role(),
                                    baseDto.isActive(),
                                    baseDto.createdAt(),
                                    baseDto.avatarUrl(),
                                    baseDto.isOnline(),
                                    baseDto.status(),
                                    doctor.getMedicalLicenseNumber(),
                                    doctor.getIsVerified()
                            );
                        }
                    }
                    return baseDto;
                })
                .toList();
        
        return PaginatedResponse.of(users, userPage.getNumber(), userPage.getSize(), userPage.getTotalElements());
    }

    @Cacheable(value = "doctors")
    public List<UserManagementDto> getAllDoctors() {
        return userRepository.findAll().stream()
                .filter(user -> user.getRole() == Role.DOCTOR)
                .map(user -> {
                    UserManagementDto baseDto = userMapper.toUserManagementDto(user);
                    // Get doctor-specific information
                    Doctor doctor = doctorRepository.findByUser(user).orElse(null);
                    if (doctor != null) {
                        return new UserManagementDto(
                                baseDto.id(),
                                baseDto.email(),
                                baseDto.firstName(),
                                baseDto.lastName(),
                                baseDto.phoneNumber(),
                                baseDto.role(),
                                baseDto.isActive(),
                                baseDto.createdAt(),
                                baseDto.avatarUrl(),
                                baseDto.isOnline(),
                                baseDto.status(),
                                doctor.getMedicalLicenseNumber(),
                                doctor.getIsVerified()
                        );
                    }
                    return new UserManagementDto(
                            baseDto.id(),
                            baseDto.email(),
                            baseDto.firstName(),
                            baseDto.lastName(),
                            baseDto.phoneNumber(),
                            baseDto.role(),
                            baseDto.isActive(),
                            baseDto.createdAt(),
                            baseDto.avatarUrl(),
                            baseDto.isOnline(),
                            baseDto.status(),
                            null,
                            null
                    );
                })
                .toList();
    }

    @Cacheable(value = "patients")
    public List<UserManagementDto> getAllPatients() {
        return userRepository.findAll().stream()
                .filter(user -> user.getRole() == Role.PATIENT)
                .map(userMapper::toUserManagementDto)
                .toList();
    }

    @Cacheable(value = "adminStats")
    public AdminStatsResponseDto getSystemStats() {
        long totalUsers = userRepository.count();
        long totalDoctors = userRepository.findAll().stream()
                .filter(user -> user.getRole() == Role.DOCTOR)
                .count();
        long totalPatients = userRepository.findAll().stream()
                .filter(user -> user.getRole() == Role.PATIENT)
                .count();
        long totalAppointments = appointmentRepository.count();
        long activeUsers = userRepository.findAll().stream()
                .filter(user -> Boolean.TRUE.equals(user.getIsActive()))
                .count();

        return new AdminStatsResponseDto(
                totalUsers,
                totalDoctors,
                totalPatients,
                totalAppointments,
                activeUsers
        );
    }

    @Transactional
    public UserManagementDto toggleUserActivity(String email, boolean isActive) {
        Optional<UserModel> user = userRepository.findByEmail(email);
        if (user.isEmpty()) {
            throw new RuntimeException("User with email " + email + " not found");
        }
        UserModel actualUser = user.get();
        actualUser.setIsActive(isActive);
        userRepository.save(actualUser);
        return userMapper.toUserManagementDto(actualUser);
    }

    @Transactional
    public void verifyDoctor(String medicalLicenseNumber, boolean isVerified) {
        Doctor doctor = doctorRepository.findByMedicalLicenseNumber(medicalLicenseNumber)
                .orElseThrow(() -> new IllegalArgumentException("Doctor not found with medical license number " + medicalLicenseNumber));

        doctor.setIsVerified(isVerified);
        doctorRepository.save(doctor);
    }

    @Transactional
    public void deleteUser(String email) {
        UserModel user = userRepository.findByEmail(email)
                .orElseThrow(() -> new RuntimeException("User with email " + email + " not found"));

        // Clean up HIPAA audit logs tied to this user
        hipaaAuditLogRepository.deleteByUserId(user.getId());

        // If the user is (or has) a patient profile, remove logs referencing that patient
        if (user.getRole() == Role.PATIENT) {
            patientRepository.findByUser(user).ifPresent(patient ->
                    hipaaAuditLogRepository.deleteByPatientId(patient.getId())
            );
        }

        // Remove breach notifications created by this user to avoid FK conflicts
        breachNotificationRepository.deleteByCreatedBy(user);

        userRepository.delete(user);
    }

    public List<AppointmentResponseDto> getAllAppointments() {
        return appointmentService.getAllAppointments();
    }

    public MetricsResponseDto getMetrics() {
        // Get counter values
        double loginAttempts = getCounterValue("telemedicine.auth.login.attempts");
        double loginSuccess = getCounterValue("telemedicine.auth.login.success");
        double loginFailure = getCounterValue("telemedicine.auth.login.failure");
        double passwordResetRequests = getCounterValue("telemedicine.auth.password.reset.requests");
        double passwordResetSuccess = getCounterValue("telemedicine.auth.password.reset.success");
        double twoFactorAuthEnabled = getCounterValue("telemedicine.auth.2fa.enabled");
        double twoFactorAuthVerified = getCounterValue("telemedicine.auth.2fa.verified");
        
        double appointmentsCreated = getCounterValue("telemedicine.appointments.created");
        double appointmentsCancelled = getCounterValue("telemedicine.appointments.cancelled");
        double appointmentsCompleted = getCounterValue("telemedicine.appointments.completed");
        
        double prescriptionsCreated = getCounterValue("telemedicine.prescriptions.created");
        
        double messagesSent = getCounterValue("telemedicine.messages.sent");
        
        double apiRequests = getCounterValue("telemedicine.api.requests");
        double apiErrors = getCounterValue("telemedicine.api.errors");
        
        double phiAccessCount = getCounterValue("telemedicine.phi.access");
        double breachNotifications = getCounterValue("telemedicine.breach.notifications");
        
        // Get gauge values
        int activeAppointments = (int) getGaugeValue("telemedicine.appointments.active", 0);
        long totalAppointments = (long) getGaugeValue("telemedicine.appointments.total", 0);
        int activePrescriptions = (int) getGaugeValue("telemedicine.prescriptions.active", 0);
        long totalPrescriptions = (long) getGaugeValue("telemedicine.prescriptions.total", 0);
        int activeUsers = (int) getGaugeValue("telemedicine.users.active", 0);
        long totalUsers = (long) getGaugeValue("telemedicine.users.total", 0);
        
        // Get timer statistics
        Map<String, Double> apiResponseTime = getTimerStats("telemedicine.api.response.time");
        Map<String, Double> databaseQueryTime = getTimerStats("telemedicine.database.query.time");
        
        return new MetricsResponseDto(
                loginAttempts,
                loginSuccess,
                loginFailure,
                passwordResetRequests,
                passwordResetSuccess,
                twoFactorAuthEnabled,
                twoFactorAuthVerified,
                appointmentsCreated,
                appointmentsCancelled,
                appointmentsCompleted,
                activeAppointments,
                totalAppointments,
                prescriptionsCreated,
                activePrescriptions,
                totalPrescriptions,
                messagesSent,
                apiRequests,
                apiErrors,
                apiResponseTime,
                databaseQueryTime,
                phiAccessCount,
                breachNotifications,
                activeUsers,
                totalUsers
        );
    }
    
    private double getCounterValue(String name) {
        try {
            var counter = meterRegistry.find(name).counter();
            return counter != null ? counter.count() : 0.0;
        } catch (Exception e) {
            return 0.0;
        }
    }
    
    private double getGaugeValue(String name, double defaultValue) {
        try {
            return meterRegistry.find(name).gauge() != null 
                    ? meterRegistry.find(name).gauge().value() 
                    : defaultValue;
        } catch (Exception e) {
            return defaultValue;
        }
    }
    
    private Map<String, Double> getTimerStats(String name) {
        Map<String, Double> stats = new HashMap<>();
        try {
            var timer = meterRegistry.find(name).timer();
            if (timer != null) {
                stats.put("count", (double) timer.count());
                stats.put("mean", timer.mean(TimeUnit.MILLISECONDS));
                stats.put("max", timer.max(TimeUnit.MILLISECONDS));
                stats.put("total", timer.totalTime(TimeUnit.MILLISECONDS));
            }
        } catch (Exception e) {
            // Return empty map if timer not found
        }
        return stats;
    }
}
