package com.dji.sample.manage.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.model.CustomClaim;
import com.dji.sample.manage.dao.IOperateRecordMapper;
import com.dji.sample.manage.dao.IUserMapper;
import com.dji.sample.manage.model.dto.OperateRecordDTO;
import com.dji.sample.manage.model.entity.OperateRecordEntity;
import com.dji.sample.manage.model.entity.UserEntity;
import com.dji.sample.manage.model.enums.OperateRecordTypeEnum;
import com.dji.sample.manage.service.IOperateRecordService;
import com.dji.sdk.common.PaginationData;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.stream.Collectors;

import static com.dji.sample.component.AuthInterceptor.TOKEN_CLAIM;

/**
 * Records user-triggered operations (flight task creation, takeoff, return home, etc.)
 * into the {@code operate_record} table.
 */
@Slf4j
@Service
public class OperateRecordServiceImpl implements IOperateRecordService {

    @Autowired
    private IOperateRecordMapper operateRecordMapper;

    @Autowired
    private IUserMapper userMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void record(HttpServletRequest request,
                       OperateRecordTypeEnum type,
                       String dockSn,
                       Object param,
                       String content) {
        try {
            CustomClaim claim = (CustomClaim) request.getAttribute(TOKEN_CLAIM);

            String userId      = claim != null ? claim.getId() : null;
            String workspaceId = claim != null ? claim.getWorkspaceId() : null;
            String ip          = getClientIp(request);

            String paramJson = null;
            if (param != null) {
                try {
                    paramJson = objectMapper.writeValueAsString(param);
                } catch (JsonProcessingException e) {
                    paramJson = param.toString();
                }
            }

            String operatingContent = (content != null && !content.isBlank())
                    ? content : type.getDescription();

            OperateRecordEntity entity = OperateRecordEntity.builder()
                    .ip(ip)
                    .workspaceId(workspaceId)
                    .userId(userId)
                    .operatingTime(System.currentTimeMillis())
                    .operatingType(type.getType())
                    .operatingParam(paramJson)
                    .operatingContent(operatingContent)
                    .dockSn(dockSn)
                    .build();

            operateRecordMapper.insert(entity);

        } catch (Exception e) {
            // Logging failure must never affect the main business flow
            log.error("[OperateRecord] Failed to record operation [{}]: {}", type, e.getMessage(), e);
        }
    }

    @Override
    public void record(HttpServletRequest request,
                       OperateRecordTypeEnum type,
                       String dockSn,
                       Object param) {
        record(request, type, dockSn, param, null);
    }

    private String getUserNameByUserId(String userId) {
        LambdaQueryWrapper<UserEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserEntity::getUserId, userId);
        UserEntity userEntity = userMapper.selectOne(queryWrapper);
        if (userEntity == null) {
            return null;
        }
        return userEntity.getUsername();
    }

    private OperateRecordDTO entity2DTO(OperateRecordEntity entity) {
        OperateRecordDTO recordDTO = BeanUtil.copyProperties(entity, OperateRecordDTO.class);

        String userName = getUserNameByUserId(entity.getUserId());
        recordDTO.setUserName(userName);
        return recordDTO;
    }

    private List<OperateRecordDTO> entity2DTO(List<OperateRecordEntity> entityList) {
        return entityList.stream().map(this::entity2DTO).collect(Collectors.toList());
    }

    @Override
    public PaginationData<OperateRecordDTO> page(String workspaceId,
                                                    String userId,
                                                    String dockSn,
                                                    String operatingType,
                                                    Long startTime,
                                                    Long endTime,
                                                    int page,
                                                    int pageSize) {


        LambdaQueryWrapper<OperateRecordEntity> wrapper = new LambdaQueryWrapper<OperateRecordEntity>()
                .eq(OperateRecordEntity::getWorkspaceId, workspaceId)
                .eq(StringUtils.isNotBlank(userId), OperateRecordEntity::getUserId, userId)
                .eq(StringUtils.isNotBlank(dockSn), OperateRecordEntity::getDockSn, dockSn)
                .eq(StringUtils.isNotBlank(operatingType), OperateRecordEntity::getOperatingType, operatingType)
                .ge(startTime != null, OperateRecordEntity::getOperatingTime, startTime)
                .lt(endTime != null, OperateRecordEntity::getOperatingTime, endTime)
                .orderByDesc(OperateRecordEntity::getOperatingTime);

        Page<OperateRecordEntity> result = operateRecordMapper.selectPage(new Page<>(page, pageSize), wrapper);

        List<OperateRecordEntity> entityList = result.getRecords();
        List<OperateRecordDTO> dtoList = entity2DTO(entityList);


        return PaginationData.of(dtoList, result.getTotal(), page, pageSize);
    }

    // -------------------------------------------------------------------------

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip != null && !ip.isBlank() && !"unknown".equalsIgnoreCase(ip)) {
            // X-Forwarded-For may contain multiple IPs; take the first one
            int comma = ip.indexOf(',');
            return comma > 0 ? ip.substring(0, comma).trim() : ip.trim();
        }
        ip = request.getHeader("X-Real-IP");
        if (ip != null && !ip.isBlank() && !"unknown".equalsIgnoreCase(ip)) {
            return ip.trim();
        }
        return request.getRemoteAddr();
    }
}
