package shop.alien.storeplatform.aspect;

import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import shop.alien.entity.result.R;
import shop.alien.entity.store.StorePlatformMenu;
import shop.alien.entity.store.StorePlatformRole;
import shop.alien.entity.store.StoreUser;
import shop.alien.entity.storePlatform.StorePlatformOperationLog;
import shop.alien.mapper.StorePlatformMenuMapper;
import shop.alien.mapper.StoreUserMapper;
import shop.alien.storeplatform.annotation.PlatformOperationLog;
import shop.alien.storeplatform.dto.CreateAccountDto;
import shop.alien.storeplatform.dto.CreateRoleDto;
import shop.alien.storeplatform.dto.UpdateRoleDto;
import shop.alien.storeplatform.service.PlatformOperationLogService;
import com.alibaba.fastjson.JSONObject;
import shop.alien.storeplatform.service.StorePlatformRoleMenuService;
import shop.alien.storeplatform.service.StorePlatformRoleQueryService;
import shop.alien.storeplatform.util.LoginUserUtil;
import shop.alien.storeplatform.dto.UpdateAccountDto;
import shop.alien.entity.store.StorePlatformUserRole;
import shop.alien.mapper.StorePlatformUserRoleMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 平台操作记录切面。
 * <p>
 * 通过 {@link PlatformOperationLog} 注解拦截业务方法，记录操作内容、入参、操作者与请求信息。
 * </p>
 *
 * @author system
 * @since 2025-01-XX
 */
@Slf4j
@Aspect
@Component
@Order(10)
@RequiredArgsConstructor
public class PlatformOperationLogAspect {

    private final PlatformOperationLogService platformOperationLogService;
    private final StorePlatformRoleMenuService storePlatformRoleMenuService;
    private final StorePlatformMenuMapper storePlatformMenuMapper;
    private final StoreUserMapper storeUserMapper;
    private final StorePlatformUserRoleMapper storePlatformUserRoleMapper;
    private final StorePlatformRoleQueryService storePlatformRoleQueryService;
    private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    private final TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");

    /**
     * 操作记录切点：拦截带 {@link PlatformOperationLog} 注解的方法。
     */
    @Pointcut("@annotation(shop.alien.storeplatform.annotation.PlatformOperationLog)")
    public void operationLogPointcut() {
    }

    /**
     * 环绕通知：先执行业务逻辑，再生成并保存操作日志。
     *
     * @param joinPoint 连接点
     * @return 业务方法返回值
     * @throws Throwable 业务方法执行异常
     */
    @Around("operationLogPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        PlatformOperationLog annotation = method.getAnnotation(PlatformOperationLog.class);
        // 变更前快照：用于“修改子账号”差异日志
        SubAccountSnapshot beforeSnapshot = buildSubAccountSnapshot(annotation, joinPoint.getArgs());

        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Exception e) {
            throw e;
        }

        if (annotation != null && shouldRecord(result)) {
            try {
                StorePlatformOperationLog logRecord = buildLogRecord(joinPoint, annotation, result, beforeSnapshot);
                platformOperationLogService.save(logRecord);
            } catch (Exception e) {
                log.warn("平台操作记录失败: {}", e.getMessage());
            }
        }

        return result;
    }

    /**
     * 判断是否需要记录操作日志。
     * <p>
     * 返回值为 {@link R} 时，只有成功结果才记录。
     * </p>
     *
     * @param result 业务方法返回值
     * @return 是否记录日志
     */
    private boolean shouldRecord(Object result) {
        if (result instanceof R) {
            return R.isSuccess((R<?>) result);
        }
        return true;
    }

    /**
     * 构建操作日志实体。
     *
     * @param joinPoint     连接点
     * @param annotation    注解信息
     * @param result        业务方法返回值
     * @param beforeSnapshot 子账号变更前快照
     * @return 操作日志实体
     */
    private StorePlatformOperationLog buildLogRecord(ProceedingJoinPoint joinPoint, PlatformOperationLog annotation, Object result,
                                                     SubAccountSnapshot beforeSnapshot) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

        // SpEL 上下文：支持 #p0/#arg0/参数名/返回值
        EvaluationContext context = new StandardEvaluationContext();
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                context.setVariable("p" + i, args[i]);
                context.setVariable("arg" + i, args[i]);
                if (parameterNames != null && parameterNames.length > i) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }
        }
        context.setVariable("result", result);

        StorePlatformOperationLog logRecord = new StorePlatformOperationLog();
        logRecord.setOperationModule(annotation.module());
        logRecord.setOperationType(annotation.type());
        logRecord.setOperationTime(new Date());

        String content = resolveContent(annotation, context, args, beforeSnapshot);
        logRecord.setOperationContent(content);
        logRecord.setOperationParams(buildParamsJson(parameterNames, args));

        fillOperatorInfo(logRecord);
        fillRequestInfo(logRecord);
        return logRecord;
    }

    /**
     * 填充操作者信息。
     *
     * @param logRecord 操作日志实体
     */
    private void fillOperatorInfo(StorePlatformOperationLog logRecord) {
        try {
            JSONObject userInfo = LoginUserUtil.getCurrentUserInfo();
            if (userInfo != null) {
                logRecord.setOperatorId(userInfo.getString("userId"));
                String account = userInfo.getString("phone");
                if (!StringUtils.hasText(account)) {
                    account = userInfo.getString("account");
                }
                logRecord.setOperatorAccount(account);
                String name = userInfo.getString("userName");
                if (!StringUtils.hasText(name)) {
                    name = userInfo.getString("name");
                }
                logRecord.setOperatorName(name);
                Integer storeId = LoginUserUtil.getCurrentStoreId();
                if (storeId != null) {
                    logRecord.setStoreId(storeId);
                }
                String userType = userInfo.getString("userType");
                logRecord.setUserType(StringUtils.hasText(userType) ? userType : "platform");
            }
        } catch (Exception e) {
            log.debug("填充操作人信息失败", e);
        }
    }

    /**
     * 填充请求信息。
     *
     * @param logRecord 操作日志实体
     */
    private void fillRequestInfo(StorePlatformOperationLog logRecord) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return;
            }
            HttpServletRequest request = attributes.getRequest();
            // 记录请求方法/路径/IP
            logRecord.setRequestMethod(request.getMethod());
            logRecord.setRequestPath(request.getRequestURI());
            logRecord.setIpAddress(getIpAddress(request));
        } catch (Exception e) {
            log.debug("填充请求信息失败", e);
        }
    }

    /**
     * 获取请求 IP 地址，优先从代理头读取。
     *
     * @param request HttpServletRequest
     * @return IP 地址
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 解析日志内容。
     * <p>
     * 优先处理账号相关自定义内容，其次为角色统一模板，最后回退到注解 SpEL。
     * </p>
     *
     * @param annotation    注解信息
     * @param context       SpEL 上下文
     * @param args          方法参数
     * @param beforeSnapshot 子账号变更前快照
     * @return 日志内容
     */
    private String resolveContent(PlatformOperationLog annotation, EvaluationContext context, Object[] args,
                                  SubAccountSnapshot beforeSnapshot) {
        // 先处理自定义的账号相关日志模板
        if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("移除角色")) {
            String content = buildAccountRoleRemoveContent(args);
            if (StringUtils.hasText(content)) {
                return content;
            }
        }
        if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("新增子账号")) {
            String content = buildCreateSubAccountContent(args);
            if (StringUtils.hasText(content)) {
                return content;
            }
        }
        if ("账号操作记录".equals(annotation.module()) && annotation.type().contains("修改子账号")) {
            String content = buildUpdateSubAccountContent(args, beforeSnapshot);
            if (StringUtils.hasText(content)) {
                return content;
            }
        }
        // 角色相关日志的统一格式
        if ("角色操作记录".equals(annotation.module())) {
            return buildRoleOperationContent(annotation.type(), args);
        }
        String content = annotation.content();
        if (StringUtils.hasText(content)) {
            Expression exp = spelExpressionParser.parseExpression(content, templateParserContext);
            String parsed = exp.getValue(context, String.class);
            return parsed != null ? parsed : annotation.type();
        }
        return annotation.type();
    }

    /**
     * 构建子账号变更前快照，仅用于“修改子账号”日志差异对比。
     *
     * @param annotation 注解信息
     * @param args       方法参数
     * @return 变更前快照
     */
    private SubAccountSnapshot buildSubAccountSnapshot(PlatformOperationLog annotation, Object[] args) {
        if (annotation == null || args == null || args.length == 0) {
            return null;
        }
        if (!"账号操作记录".equals(annotation.module()) || !annotation.type().contains("修改子账号")) {
            return null;
        }
        if (!(args[0] instanceof UpdateAccountDto)) {
            return null;
        }
        UpdateAccountDto dto = (UpdateAccountDto) args[0];
        if (dto.getUserId() == null || dto.getStoreId() == null) {
            return null;
        }
        StoreUser storeUser = storeUserMapper.selectById(dto.getUserId());
        String phone = storeUser != null ? storeUser.getPhone() : null;
        String accountName = null;
        Long roleId = null;

        LambdaQueryWrapper<StorePlatformUserRole> userRoleQuery = new LambdaQueryWrapper<>();
        userRoleQuery.eq(StorePlatformUserRole::getUserId, dto.getUserId())
                .eq(StorePlatformUserRole::getStoreId, dto.getStoreId())
                .eq(StorePlatformUserRole::getDeleteFlag, 0)
                .last("LIMIT 1");
        StorePlatformUserRole userRole = storePlatformUserRoleMapper.selectOne(userRoleQuery);
        if (userRole != null) {
            accountName = userRole.getAccountName();
            roleId = userRole.getRoleId();
        }

        if (!StringUtils.hasText(accountName) && storeUser != null) {
            accountName = StringUtils.hasText(storeUser.getName())
                    ? storeUser.getName()
                    : (StringUtils.hasText(storeUser.getNickName()) ? storeUser.getNickName() : storeUser.getPhone());
        }
        return new SubAccountSnapshot(phone, accountName, roleId);
    }

    /**
     * 构建“修改子账号”日志内容，仅记录变更字段。
     *
     * @param args           方法参数
     * @param beforeSnapshot 变更前快照
     * @return 日志内容
     */
    private String buildUpdateSubAccountContent(Object[] args, SubAccountSnapshot beforeSnapshot) {
        if (beforeSnapshot == null || args == null || args.length == 0) {
            return null;
        }
        if (!(args[0] instanceof UpdateAccountDto)) {
            return null;
        }
        UpdateAccountDto dto = (UpdateAccountDto) args[0];
        StringBuilder changed = new StringBuilder();

        if (StringUtils.hasText(dto.getPhone()) && !dto.getPhone().equals(beforeSnapshot.phone)) {
            appendChange(changed, "手机号", beforeSnapshot.phone, dto.getPhone());
        }
        if (StringUtils.hasText(dto.getAccountName()) && !dto.getAccountName().equals(beforeSnapshot.accountName)) {
            appendChange(changed, "账号名", beforeSnapshot.accountName, dto.getAccountName());
        }
        if (dto.getRoleId() != null && !dto.getRoleId().equals(beforeSnapshot.roleId)) {
            String oldRoleName = resolveRoleName(beforeSnapshot.roleId);
            String newRoleName = resolveRoleName(dto.getRoleId());
            appendChange(changed, "角色", oldRoleName, newRoleName);
        }

        if (changed.length() == 0) {
            return "修改子账号（无变更）";
        }
        return "修改子账号，" + changed;
    }

    /**
     * 构建“新增子账号”日志内容。
     *
     * @param args 方法参数
     * @return 日志内容
     */
    private String buildCreateSubAccountContent(Object[] args) {
        if (args == null || args.length == 0) {
            return null;
        }
        if (!(args[0] instanceof CreateAccountDto)) {
            return null;
        }
        CreateAccountDto dto = (CreateAccountDto) args[0];
        StringBuilder content = new StringBuilder();
        content.append("新增子账号");
        if (StringUtils.hasText(dto.getPhone())) {
            content.append("，手机号：").append(dto.getPhone());
        }
        if (StringUtils.hasText(dto.getAccountName())) {
            content.append("，账号名：").append(dto.getAccountName());
        }
        if (dto.getRoleId() != null) {
            String roleName = resolveRoleName(dto.getRoleId());
            content.append("，角色：").append(roleName);
        }
        return content.toString();
    }

    /**
     * 追加字段变更描述。
     *
     * @param builder  内容构建器
     * @param label    字段中文名
     * @param oldValue 旧值
     * @param newValue 新值
     */
    private void appendChange(StringBuilder builder, String label, String oldValue, String newValue) {
        if (builder.length() > 0) {
            builder.append("；");
        }
        builder.append(label)
                .append("：")
                .append(StringUtils.hasText(oldValue) ? oldValue : "空")
                .append("修改为")
                .append(StringUtils.hasText(newValue) ? newValue : "空");
    }

    /**
     * 子账号变更前快照。
     */
    private static class SubAccountSnapshot {
        private final String phone;
        private final String accountName;
        private final Long roleId;

        private SubAccountSnapshot(String phone, String accountName, Long roleId) {
            this.phone = phone;
            this.accountName = accountName;
            this.roleId = roleId;
        }
    }

    /**
     * 构建“移除角色”日志内容。
     *
     * @param args 方法参数
     * @return 日志内容
     */
    private String buildAccountRoleRemoveContent(Object[] args) {
        if (args == null || args.length < 2) {
            return null;
        }
        Integer userId = null;
        Long roleId = null;
        if (args[0] instanceof Integer) {
            userId = (Integer) args[0];
        }
        if (args[1] instanceof Long) {
            roleId = (Long) args[1];
        }
        if (userId == null || roleId == null) {
            return null;
        }

        String accountName = resolveAccountName(userId);
        String roleName = resolveRoleName(roleId);
        return String.format("移除用户%s的%s角色", accountName, roleName);
    }

    /**
     * 解析账号名称，允许读取已删除用户。
     *
     * @param userId 用户ID
     * @return 账号名称
     */
    private String resolveAccountName(Integer userId) {
        StoreUser storeUser = storeUserMapper.getUserIncludeDeleted(userId);
        if (storeUser == null) {
            return "未知";
        }
        String name = storeUser.getName();
        if (!StringUtils.hasText(name)) {
            name = storeUser.getNickName();
        }
        if (!StringUtils.hasText(name)) {
            name = storeUser.getPhone();
        }
        return StringUtils.hasText(name) ? name : "未知";
    }

    /**
     * 解析角色名称。
     *
     * @param roleId 角色ID
     * @return 角色名称
     */
    private String resolveRoleName(Long roleId) {
        String roleName = storePlatformRoleQueryService.getRoleNameById(roleId);
        if (!StringUtils.hasText(roleName)) {
            return "未知";
        }
        return roleName;
    }

    /**
     * 构建操作入参 JSON 字符串。
     *
     * @param parameterNames 参数名列表
     * @param args           参数值列表
     * @return JSON 字符串
     */
    private String buildParamsJson(String[] parameterNames, Object[] args) {
        try {
            if (args == null || args.length == 0) {
                return null;
            }
            if (parameterNames == null || parameterNames.length == 0) {
                return JSON.toJSONString(args);
            }
            java.util.Map<String, Object> params = new java.util.LinkedHashMap<>();
            for (int i = 0; i < args.length; i++) {
                String name = parameterNames.length > i ? parameterNames[i] : "arg" + i;
                params.put(name, args[i]);
            }
            return JSON.toJSONString(params);
        } catch (Exception e) {
            log.debug("序列化操作入参失败", e);
            return null;
        }
    }

    private String buildRoleOperationContent(String operationType, Object[] args) {
        String roleName = null;
        List<Long> menuIds = Collections.emptyList();
        Long roleId = null;

        if (args != null && args.length > 0) {
            Object firstArg = args[0];
            if (firstArg instanceof StorePlatformRole) {
                StorePlatformRole role = (StorePlatformRole) firstArg;
                roleName = role.getRoleName();
            } else if (firstArg instanceof CreateRoleDto) {
                CreateRoleDto dto = (CreateRoleDto) firstArg;
                roleName = dto.getRoleName();
                menuIds = dto.getMenuIds() != null ? dto.getMenuIds() : Collections.emptyList();
            } else if (firstArg instanceof UpdateRoleDto) {
                UpdateRoleDto dto = (UpdateRoleDto) firstArg;
                roleName = dto.getRoleName();
                roleId = dto.getRoleId();
                menuIds = dto.getMenuIds() != null ? dto.getMenuIds() : Collections.emptyList();
            } else if (firstArg instanceof Long) {
                roleId = (Long) firstArg;
            }
        }

        if ((roleName == null || roleName.trim().isEmpty()) && roleId != null) {
             roleName = resolveRoleName(roleId);
        }

        if ((menuIds == null || menuIds.isEmpty()) && roleId != null) {
            menuIds = storePlatformRoleMenuService.getMenuIdsByRoleId(roleId);
        }

        List<String> menuNames = resolveMenuNames(menuIds);
        int count = menuNames.size();
        String menuSummary = menuNames.isEmpty() ? "无权限" : menuNames.get(0);
        String roleLabel = StringUtils.hasText(roleName) ? roleName : "未知";
        String type = normalizeRoleOperationType(operationType);
        return String.format("%s%s角色标签，包含%s等%d个权限", type, roleLabel, menuSummary, count);
    }

    private List<String> resolveMenuNames(List<Long> menuIds) {
        if (menuIds == null || menuIds.isEmpty()) {
            return Collections.emptyList();
        }
        return storePlatformMenuMapper.selectBatchIds(menuIds).stream()
                .map(StorePlatformMenu::getMenuName)
                .filter(StringUtils::hasText)
                .collect(Collectors.toList());
    }

    private String normalizeRoleOperationType(String operationType) {
        if (operationType == null) {
            return "操作";
        }
        if (operationType.contains("新增")) {
            return "新增";
        }
        if (operationType.contains("修改")) {
            return "修改";
        }
        if (operationType.contains("删除")) {
            return "删除";
        }
        return operationType;
    }
}
