package com.mvrent.carrental.config;

import com.mvrent.carrental.service.AuditService;
import lombok.RequiredArgsConstructor;
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.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Automatically records every mutating service-layer call (save / create /
 * update / delete / add / remove / generate) to the audit trail. Argument
 * <em>values</em> are never logged (to avoid leaking passwords etc.) — only the
 * action, entity type and id.
 */
@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {

    private final AuditService auditService;

    @Pointcut("execution(* com.mvrent.carrental.service.*.*(..))")
    void serviceLayer() {}

    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void afterMutation(JoinPoint jp, Object result) {
        String method = jp.getSignature().getName();
        String action = mapAction(method);
        if (action == null) return;

        String cls = jp.getSignature().getDeclaringType().getSimpleName();
        // Avoid recursion / noise from the auditing beans themselves.
        if (cls.equals("AuditService") || cls.equals("AuditWriter")) return;

        String entityType = cls.endsWith("Service") ? cls.substring(0, cls.length() - "Service".length()) : cls;
        String entityId = extractId(result, jp.getArgs());
        auditService.log(action, entityType, entityId, method);
    }

    private static String mapAction(String m) {
        if (m.startsWith("delete") || m.startsWith("remove")) return "DELETE";
        if (m.startsWith("add") || m.startsWith("create") || m.startsWith("register")) return "CREATE";
        if (m.startsWith("generate")) return "GENERATE";
        if (m.startsWith("save") || m.startsWith("update") || m.startsWith("change")) return "UPDATE";
        return null;
    }

    /** Best-effort id: from the returned entity, else the first Long argument. */
    private static String extractId(Object result, Object[] args) {
        String fromResult = idOf(result);
        if (fromResult != null) return fromResult;
        if (args != null) {
            for (Object a : args) {
                if (a instanceof Long l) return String.valueOf(l);
            }
            for (Object a : args) {
                String id = idOf(a);
                if (id != null) return id;
            }
        }
        return null;
    }

    private static String idOf(Object obj) {
        if (obj == null) return null;
        try {
            Method getId = obj.getClass().getMethod("getId");
            Object id = getId.invoke(obj);
            return id != null ? String.valueOf(id) : null;
        } catch (Exception ignored) {
            return null;
        }
    }
}
