package com.flightticket.aspect;

import com.flightticket.service.AuditLogService;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * Aspect for logging administrative actions
 * Requirements: 12.4
 */
@Aspect
@Component
public class AdministrativeActionLoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(AdministrativeActionLoggingAspect.class);
    
    private final AuditLogService auditLogService;
    
    public AdministrativeActionLoggingAspect(AuditLogService auditLogService) {
        this.auditLogService = auditLogService;
    }
    
    /**
     * Pointcut for user creation
     */
    @Pointcut("execution(* com.flightticket.service.UserManagementService.createUser(..))")
    public void createUserMethod() {}
    
    /**
     * Pointcut for user update
     */
    @Pointcut("execution(* com.flightticket.service.UserManagementService.updateUser(..))")
    public void updateUserMethod() {}
    
    /**
     * Pointcut for user deletion
     */
    @Pointcut("execution(* com.flightticket.service.UserManagementService.deleteUser(..))")
    public void deleteUserMethod() {}
    
    /**
     * Pointcut for role assignment
     */
    @Pointcut("execution(* com.flightticket.service.UserManagementService.assignRole(..))")
    public void assignRoleMethod() {}
    
    /**
     * Pointcut for agency verification
     */
    @Pointcut("execution(* com.flightticket.service.AgencyManagementService.verifyAgency(..))")
    public void verifyAgencyMethod() {}
    
    /**
     * Pointcut for agency activation
     */
    @Pointcut("execution(* com.flightticket.service.AgencyManagementService.activateAgency(..))")
    public void activateAgencyMethod() {}
    
    /**
     * Pointcut for agency deactivation
     */
    @Pointcut("execution(* com.flightticket.service.AgencyManagementService.deactivateAgency(..))")
    public void deactivateAgencyMethod() {}
    
    /**
     * Pointcut for markup update
     */
    @Pointcut("execution(* com.flightticket.service.AgencyManagementService.updateMarkup(..))")
    public void updateMarkupMethod() {}
    
    /**
     * Pointcut for recharge approval
     */
    @Pointcut("execution(* com.flightticket.service.WalletService.approveRecharge(..))")
    public void approveRechargeMethod() {}
    
    /**
     * Pointcut for recharge rejection
     */
    @Pointcut("execution(* com.flightticket.service.WalletService.rejectRecharge(..))")
    public void rejectRechargeMethod() {}
    
    /**
     * Log user creation
     */
    @AfterReturning(pointcut = "createUserMethod()", returning = "result")
    public void logUserCreation(JoinPoint joinPoint, Object result) {
        logAdminAction(joinPoint, "USER_CREATED", "USER", extractEntityId(result));
    }
    
    /**
     * Log user update
     */
    @AfterReturning(pointcut = "updateUserMethod()", returning = "result")
    public void logUserUpdate(JoinPoint joinPoint, Object result) {
        logAdminAction(joinPoint, "USER_UPDATED", "USER", extractEntityId(result));
    }
    
    /**
     * Log user deletion
     */
    @AfterReturning(pointcut = "deleteUserMethod()")
    public void logUserDeletion(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String userId = args.length > 0 ? args[0].toString() : null;
        logAdminAction(joinPoint, "USER_DELETED", "USER", userId);
    }
    
    /**
     * Log role assignment
     */
    @AfterReturning(pointcut = "assignRoleMethod()")
    public void logRoleAssignment(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String userId = args.length > 0 ? args[0].toString() : null;
        logAdminAction(joinPoint, "ROLE_ASSIGNED", "USER", userId);
    }
    
    /**
     * Log agency verification
     */
    @AfterReturning(pointcut = "verifyAgencyMethod()")
    public void logAgencyVerification(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String agencyId = args.length > 0 ? args[0].toString() : null;
        logAdminAction(joinPoint, "AGENCY_VERIFIED", "AGENCY", agencyId);
    }
    
    /**
     * Log agency activation
     */
    @AfterReturning(pointcut = "activateAgencyMethod()")
    public void logAgencyActivation(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String agencyId = args.length > 0 ? args[0].toString() : null;
        logAdminAction(joinPoint, "AGENCY_ACTIVATED", "AGENCY", agencyId);
    }
    
    /**
     * Log agency deactivation
     */
    @AfterReturning(pointcut = "deactivateAgencyMethod()")
    public void logAgencyDeactivation(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String agencyId = args.length > 0 ? args[0].toString() : null;
        Map<String, Object> details = new HashMap<>();
        if (args.length > 2) {
            details.put("reason", args[2].toString());
        }
        logAdminActionWithDetails(joinPoint, "AGENCY_DEACTIVATED", "AGENCY", agencyId, details);
    }
    
    /**
     * Log markup update
     */
    @AfterReturning(pointcut = "updateMarkupMethod()")
    public void logMarkupUpdate(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String agencyId = args.length > 0 ? args[0].toString() : null;
        Map<String, Object> details = new HashMap<>();
        if (args.length > 1) {
            details.put("newMarkupPercentage", args[1].toString());
        }
        logAdminActionWithDetails(joinPoint, "MARKUP_UPDATED", "AGENCY", agencyId, details);
    }
    
    /**
     * Log recharge approval
     */
    @AfterReturning(pointcut = "approveRechargeMethod()")
    public void logRechargeApproval(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String requestId = args.length > 0 ? args[0].toString() : null;
        logAdminAction(joinPoint, "RECHARGE_APPROVED", "RECHARGE_REQUEST", requestId);
    }
    
    /**
     * Log recharge rejection
     */
    @AfterReturning(pointcut = "rejectRechargeMethod()")
    public void logRechargeRejection(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        String requestId = args.length > 0 ? args[0].toString() : null;
        Map<String, Object> details = new HashMap<>();
        if (args.length > 2) {
            details.put("reason", args[2].toString());
        }
        logAdminActionWithDetails(joinPoint, "RECHARGE_REJECTED", "RECHARGE_REQUEST", requestId, details);
    }
    
    /**
     * Log administrative action
     */
    private void logAdminAction(JoinPoint joinPoint, String action, String entityType, String entityId) {
        logAdminActionWithDetails(joinPoint, action, entityType, entityId, new HashMap<>());
    }
    
    /**
     * Log administrative action with details
     */
    private void logAdminActionWithDetails(
            JoinPoint joinPoint, 
            String action, 
            String entityType, 
            String entityId,
            Map<String, Object> additionalDetails) {
        try {
            UUID userId = getCurrentUserId();
            UUID agencyId = getCurrentAgencyId();
            
            HttpServletRequest request = getCurrentRequest();
            String ipAddress = getClientIpAddress(request);
            String userAgent = getUserAgent(request);
            
            Map<String, Object> details = new HashMap<>(additionalDetails);
            details.put("method", joinPoint.getSignature().getName());
            
            auditLogService.logAdministrativeAction(
                userId,
                agencyId,
                action,
                entityType,
                entityId,
                details,
                ipAddress,
                userAgent
            );
        } catch (Exception e) {
            logger.error("Error logging administrative action", e);
        }
    }
    
    /**
     * Extract entity ID from result object
     */
    private String extractEntityId(Object result) {
        if (result == null) {
            return null;
        }
        
        try {
            // Try to get ID using reflection
            var idMethod = result.getClass().getMethod("getId");
            Object id = idMethod.invoke(result);
            return id != null ? id.toString() : null;
        } catch (Exception e) {
            logger.debug("Could not extract entity ID from result", e);
            return null;
        }
    }
    
    /**
     * Get current user ID from security context
     */
    private UUID getCurrentUserId() {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
                Jwt jwt = (Jwt) authentication.getPrincipal();
                String userId = jwt.getClaimAsString("sub");
                if (userId != null) {
                    return UUID.fromString(userId);
                }
            }
        } catch (Exception e) {
            logger.debug("Could not extract user ID from security context", e);
        }
        return null;
    }
    
    /**
     * Get current agency ID from security context
     */
    private UUID getCurrentAgencyId() {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
                Jwt jwt = (Jwt) authentication.getPrincipal();
                String agencyId = jwt.getClaimAsString("agency_id");
                if (agencyId != null) {
                    return UUID.fromString(agencyId);
                }
            }
        } catch (Exception e) {
            logger.debug("Could not extract agency ID from security context", e);
        }
        return null;
    }
    
    /**
     * Get current HTTP request
     */
    private HttpServletRequest getCurrentRequest() {
        ServletRequestAttributes attributes = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes != null ? attributes.getRequest() : null;
    }
    
    /**
     * Extract client IP address from request
     */
    private String getClientIpAddress(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        
        String ipAddress = request.getHeader("X-Forwarded-For");
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("X-Real-IP");
        }
        if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }
        
        if (ipAddress != null && ipAddress.contains(",")) {
            ipAddress = ipAddress.split(",")[0].trim();
        }
        
        return ipAddress;
    }
    
    /**
     * Extract user agent from request
     */
    private String getUserAgent(HttpServletRequest request) {
        if (request == null) {
            return null;
        }
        return request.getHeader("User-Agent");
    }
}
