package com.gnx.telemedicine.aspect;

import com.gnx.telemedicine.annotation.LogPhiAccess;
import com.gnx.telemedicine.model.enums.ActionType;
import com.gnx.telemedicine.service.HipaaAuditService;
import com.gnx.telemedicine.service.PhiAccessLogService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.UUID;

@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class HipaaAuditAspect {
    
    private final HipaaAuditService hipaaAuditService;
    private final PhiAccessLogService phiAccessLogService;

    @AfterReturning("@annotation(logPhiAccess)")
    public void logPhiAccess(JoinPoint joinPoint, LogPhiAccess logPhiAccess) {
        try {
            // Get current user
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null || !authentication.isAuthenticated() || "anonymousUser".equals(authentication.getName())) {
                return; // Skip logging for unauthenticated users
            }
            
            String userEmail = authentication.getName();
            
            // Get HTTP request
            HttpServletRequest request = getHttpServletRequest();
            if (request == null) {
                return;
            }
            
            // Extract parameters from method signature
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Parameter[] parameters = method.getParameters();
            Object[] args = joinPoint.getArgs();
            
            // Extract patient ID - try multiple methods
            UUID patientId = extractPatientId(parameters, args, logPhiAccess.patientIdParam());
            
            // If patient ID not found directly, try to extract from request DTO
            if (patientId == null && !logPhiAccess.patientIdParam().isEmpty()) {
                patientId = extractPatientIdFromDto(args, logPhiAccess.patientIdParam());
            }
            
            // If still not found, try common patterns
            if (patientId == null) {
                // Look for any UUID parameter that might be a patient ID
                for (int i = 0; i < parameters.length; i++) {
                    String paramName = parameters[i].getName().toLowerCase();
                    if (paramName.contains("patient") || paramName.equals("patientid")) {
                        if (args[i] instanceof UUID) {
                            patientId = (UUID) args[i];
                            break;
                        } else if (args[i] instanceof String) {
                            try {
                                patientId = UUID.fromString((String) args[i]);
                                break;
                            } catch (Exception e) {
                                // Ignore
                            }
                        }
                    }
                }
            }
            
            // Extract resource ID
            UUID resourceId = extractResourceId(parameters, args, logPhiAccess.resourceIdParam());
            if (resourceId == null && args.length > 0) {
                // Try common parameter names
                for (int i = 0; i < parameters.length; i++) {
                    String paramName = parameters[i].getName().toLowerCase();
                    if ((paramName.equals("id") || paramName.equals("recordid") || paramName.equals("prescriptionid")) 
                        && args[i] instanceof UUID) {
                        resourceId = (UUID) args[i];
                        break;
                    }
                }
            }
            
            // If we have resource ID but no patient ID, try to get patient ID from the response
            // This would require executing the method first, so we'll skip it for now
            // In production, you might want to fetch patient ID from the database using resource ID
            
            // Determine action type from method name
            ActionType actionType = determineActionType(method.getName());
            
            // Create details map
            Map<String, Object> details = new HashMap<>();
            details.put("method", method.getName());
            details.put("className", joinPoint.getTarget().getClass().getSimpleName());
            if (resourceId != null) {
                details.put("resourceId", resourceId.toString());
            }
            if (logPhiAccess.accessedFields().length > 0) {
                details.put("accessedFields", Arrays.asList(logPhiAccess.accessedFields()));
            }
            
            // Log HIPAA audit
            hipaaAuditService.logAccess(
                userEmail,
                actionType,
                logPhiAccess.resourceType(),
                resourceId != null ? resourceId : UUID.randomUUID(), // Fallback to random UUID if not found
                patientId,
                details,
                request
            );
            
            // Log PHI access if patient ID is available
            if (patientId != null) {
                List<String> accessedFields = new ArrayList<>();
                if (logPhiAccess.accessedFields().length > 0) {
                    accessedFields.addAll(Arrays.asList(logPhiAccess.accessedFields()));
                } else {
                    accessedFields.add("ALL"); // If no specific fields, assume all fields accessed
                }
                
                phiAccessLogService.logPhiAccess(
                    userEmail,
                    patientId,
                    logPhiAccess.accessType(),
                    accessedFields,
                    "Access via " + method.getName(),
                    request
                );
            }
            
        } catch (Exception e) {
            // Log error but don't throw - audit logging should not break the main flow
            log.error("Error logging PHI access: {}", e.getMessage(), e);
        }
    }
    
    private UUID extractPatientId(Parameter[] parameters, Object[] args, String paramName) {
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getName().equals(paramName) || 
                parameters[i].getName().equals("patientId")) {
                if (args[i] instanceof UUID) {
                    return (UUID) args[i];
                } else if (args[i] instanceof String) {
                    try {
                        return UUID.fromString((String) args[i]);
                    } catch (Exception e) {
                        // Ignore
                    }
                }
            }
        }
        return null;
    }
    
    private UUID extractResourceId(Parameter[] parameters, Object[] args, String paramName) {
        if (paramName == null || paramName.isEmpty()) {
            return null;
        }
        
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getName().equals(paramName) || parameters[i].getName().equals("id")) {
                if (args[i] instanceof UUID) {
                    return (UUID) args[i];
                } else if (args[i] instanceof String) {
                    try {
                        return UUID.fromString((String) args[i]);
                    } catch (Exception e) {
                        // Ignore
                    }
                }
            }
        }
        return null;
    }
    
    private UUID extractPatientIdFromDto(Object[] args, String paramName) {
        // Try to extract from DTO objects
        for (Object arg : args) {
            if (arg == null) continue;
            
            try {
                // For record DTOs, try to get patientId() method (records use methods, not getters)
                java.lang.reflect.Method[] methods = arg.getClass().getMethods();
                for (java.lang.reflect.Method method : methods) {
                    String methodName = method.getName().toLowerCase();
                    // Try both getter pattern and record accessor pattern
                    if ((methodName.equals("getpatientid") || 
                         methodName.equals("patientid") ||
                         methodName.equals("patient_id")) 
                        && method.getParameterCount() == 0) {
                        Object result = method.invoke(arg);
                        if (result instanceof UUID) {
                            return (UUID) result;
                        } else if (result instanceof String) {
                            try {
                                return UUID.fromString((String) result);
                            } catch (Exception e) {
                                // Ignore
                            }
                        }
                    }
                }
                
                // Also try to access fields directly (for records)
                try {
                    java.lang.reflect.Field[] fields = arg.getClass().getDeclaredFields();
                    for (java.lang.reflect.Field field : fields) {
                        String fieldName = field.getName().toLowerCase();
                        if (fieldName.equals("patientid") || fieldName.equals("patient_id")) {
                            field.setAccessible(true);
                            Object value = field.get(arg);
                            if (value instanceof UUID) {
                                return (UUID) value;
                            } else if (value instanceof String) {
                                try {
                                    return UUID.fromString((String) value);
                                } catch (Exception e) {
                                    // Ignore
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    // Ignore
                }
            } catch (Exception e) {
                // Ignore reflection errors
            }
        }
        return null;
    }
    
    private ActionType determineActionType(String methodName) {
        String lowerMethodName = methodName.toLowerCase();
        if (lowerMethodName.contains("get") || lowerMethodName.contains("find") || lowerMethodName.contains("retrieve")) {
            return ActionType.VIEW;
        } else if (lowerMethodName.contains("create") || lowerMethodName.contains("save") || lowerMethodName.contains("add")) {
            return ActionType.CREATE;
        } else if (lowerMethodName.contains("update") || lowerMethodName.contains("modify") || lowerMethodName.contains("change")) {
            return ActionType.UPDATE;
        } else if (lowerMethodName.contains("delete") || lowerMethodName.contains("remove")) {
            return ActionType.DELETE;
        } else if (lowerMethodName.contains("export") || lowerMethodName.contains("download")) {
            return ActionType.EXPORT;
        } else if (lowerMethodName.contains("print")) {
            return ActionType.PRINT;
        }
        return ActionType.VIEW; // Default to VIEW
    }
    
    private HttpServletRequest getHttpServletRequest() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes != null ? attributes.getRequest() : null;
        } catch (Exception e) {
            return null;
        }
    }
}

