package com.flightticket.aspect;

import com.flightticket.dto.wallet.TransactionResult;
import com.flightticket.service.AuditLogService;
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 java.util.UUID;

/**
 * Aspect for logging wallet transactions
 * Requirements: 12.3
 */
@Aspect
@Component
public class WalletTransactionLoggingAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(WalletTransactionLoggingAspect.class);
    
    private final AuditLogService auditLogService;
    
    public WalletTransactionLoggingAspect(AuditLogService auditLogService) {
        this.auditLogService = auditLogService;
    }
    
    /**
     * Pointcut for wallet credit operations
     */
    @Pointcut("execution(* com.flightticket.service.WalletService.creditWallet(..))")
    public void creditWalletMethod() {}
    
    /**
     * Pointcut for wallet debit operations
     */
    @Pointcut("execution(* com.flightticket.service.WalletService.debitWallet(..))")
    public void debitWalletMethod() {}
    
    /**
     * Pointcut for fund reservation
     */
    @Pointcut("execution(* com.flightticket.service.WalletService.reserveFunds(..))")
    public void reserveFundsMethod() {}
    
    /**
     * Pointcut for fund release
     */
    @Pointcut("execution(* com.flightticket.service.WalletService.releaseFunds(..))")
    public void releaseFundsMethod() {}
    
    /**
     * Log wallet credit transaction
     */
    @AfterReturning(pointcut = "creditWalletMethod()", returning = "result")
    public void logCreditTransaction(JoinPoint joinPoint, TransactionResult result) {
        logWalletTransaction(joinPoint, result, "CREDIT");
    }
    
    /**
     * Log wallet debit transaction
     */
    @AfterReturning(pointcut = "debitWalletMethod()", returning = "result")
    public void logDebitTransaction(JoinPoint joinPoint, TransactionResult result) {
        logWalletTransaction(joinPoint, result, "DEBIT");
    }
    
    /**
     * Log fund reservation
     */
    @AfterReturning(pointcut = "reserveFundsMethod()", returning = "result")
    public void logReserveFunds(JoinPoint joinPoint, TransactionResult result) {
        logWalletTransaction(joinPoint, result, "RESERVE");
    }
    
    /**
     * Log fund release
     */
    @AfterReturning(pointcut = "releaseFundsMethod()")
    public void logReleaseFunds(JoinPoint joinPoint) {
        try {
            Object[] args = joinPoint.getArgs();
            if (args.length > 0) {
                String reservationId = args[0].toString();
                UUID userId = getCurrentUserId();
                UUID agencyId = getCurrentAgencyId();
                
                // For release, we don't have full transaction details
                // Log with minimal information
                auditLogService.logWalletTransaction(
                    userId,
                    agencyId,
                    UUID.fromString(reservationId),
                    "RELEASE",
                    "N/A",
                    "N/A",
                    "N/A",
                    reservationId
                );
            }
        } catch (Exception e) {
            logger.error("Error logging fund release", e);
        }
    }
    
    /**
     * Log wallet transaction with details
     */
    private void logWalletTransaction(JoinPoint joinPoint, TransactionResult result, String transactionType) {
        try {
            if (result == null) {
                return;
            }
            
            Object[] args = joinPoint.getArgs();
            String agencyIdStr = args.length > 0 ? args[0].toString() : null;
            String amount = args.length > 1 ? args[1].toString() : null;
            String reference = args.length > 2 ? args[2].toString() : null;
            
            UUID userId = getCurrentUserId();
            UUID agencyId = agencyIdStr != null ? UUID.fromString(agencyIdStr) : getCurrentAgencyId();
            
            // Extract transaction details from result
            UUID transactionId = extractTransactionId(result);
            String balanceBefore = extractBalanceBefore(result);
            String balanceAfter = extractBalanceAfter(result);
            
            auditLogService.logWalletTransaction(
                userId,
                agencyId,
                transactionId,
                transactionType,
                amount != null ? amount : "N/A",
                balanceBefore != null ? balanceBefore : "N/A",
                balanceAfter != null ? balanceAfter : "N/A",
                reference
            );
        } catch (Exception e) {
            logger.error("Error logging wallet transaction", e);
        }
    }
    
    /**
     * Extract transaction ID from result
     */
    private UUID extractTransactionId(TransactionResult result) {
        try {
            var method = result.getClass().getMethod("getTransactionId");
            Object id = method.invoke(result);
            return id != null ? (UUID) id : UUID.randomUUID();
        } catch (Exception e) {
            logger.debug("Could not extract transaction ID", e);
            return UUID.randomUUID();
        }
    }
    
    /**
     * Extract balance before from result
     */
    private String extractBalanceBefore(TransactionResult result) {
        try {
            var method = result.getClass().getMethod("getBalanceBefore");
            Object balance = method.invoke(result);
            return balance != null ? balance.toString() : null;
        } catch (Exception e) {
            logger.debug("Could not extract balance before", e);
            return null;
        }
    }
    
    /**
     * Extract balance after from result
     */
    private String extractBalanceAfter(TransactionResult result) {
        try {
            var method = result.getClass().getMethod("getBalanceAfter");
            Object balance = method.invoke(result);
            return balance != null ? balance.toString() : null;
        } catch (Exception e) {
            logger.debug("Could not extract balance after", 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;
    }
}
