package com.purui.prims.headless.server.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.purui.prims.auth.api.authentication.pojo.User;
import com.purui.prims.common.pojo.exception.InvalidArgumentException;
import com.purui.prims.common.pojo.exception.InvalidPermissionException;
import com.purui.prims.common.util.BeanMapper;
import com.purui.prims.common.util.PageUtils;
import com.purui.prims.common.util.SignatureUtils;
import com.purui.prims.headless.api.pojo.AppAuthConfig;
import com.purui.prims.headless.api.pojo.enums.AppAuthType;
import com.purui.prims.headless.api.pojo.enums.AppStatus;
import com.purui.prims.headless.api.pojo.request.AppQueryReq;
import com.purui.prims.headless.api.pojo.request.AppReq;
import com.purui.prims.headless.api.pojo.response.AppDetailResp;
import com.purui.prims.headless.api.pojo.response.AppResp;
import com.purui.prims.headless.api.pojo.response.DataSetResp;
import com.purui.prims.headless.api.pojo.response.DimensionResp;
import com.purui.prims.headless.api.pojo.response.MetricResp;
import com.purui.prims.headless.server.persistence.dataobject.AppAuthDO;
import com.purui.prims.headless.server.persistence.dataobject.AppDO;
import com.purui.prims.headless.server.persistence.mapper.AppAuthDOMapper;
import com.purui.prims.headless.server.persistence.mapper.AppMapper;
import com.purui.prims.headless.server.pojo.MetaFilter;
import com.purui.prims.headless.server.service.AppService;
import com.purui.prims.headless.server.service.DataSetService;
import com.purui.prims.headless.server.service.DimensionService;
import com.purui.prims.headless.server.service.MetricService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.security.SecureRandom;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 应用服务接口实现
 *
 * @author charlie
 * @date 2024/6/25
 */
@Service
public class AppServiceImpl extends ServiceImpl<AppMapper, AppDO> implements AppService {

    private AppMapper appMapper;

    private AppAuthDOMapper appAuthDOMapper;

    private MetricService metricService;

    private DimensionService dimensionService;

    private DataSetService dataSetService;

    public AppServiceImpl(AppMapper appMapper, AppAuthDOMapper appAuthDOMapper, MetricService metricService,
                          DimensionService dimensionService, DataSetService dataSetService) {
        this.appMapper = appMapper;
        this.appAuthDOMapper = appAuthDOMapper;
        this.metricService = metricService;
        this.dimensionService = dimensionService;
        this.dataSetService = dataSetService;
    }

    @Override
    public AppDetailResp save(AppReq app, User user) {
        app.createdBy(user.getUserName());
        AppDO appDO = new AppDO();
        BeanMapper.mapper(app, appDO);
        appDO.setStatus(AppStatus.INIT.getCode());
        appDO.setAppSecret(getUniqueId());
        appDO.setAppId(generateAppKey(app.getName()));
        appMapper.insert(appDO);
        return convertDetail(appDO);
    }

    // 生成AppId
    private String generateAppKey(String appName) {
        // 当前时间戳
        long timestamp = Instant.now().getEpochSecond();
        // 随机数生成器
        SecureRandom random = new SecureRandom();
        // 结合开发者ID、应用名、时间戳和随机数生成字符串
        StringBuilder keyBuilder = new StringBuilder();
        keyBuilder.append(appName).append(timestamp).append(random.nextLong());
        // 使用SHA-256进行哈希，增强安全性
        return SignatureUtils.encryptAppId(keyBuilder.toString());
    }

    @Override
    public AppDetailResp updateConfig(Long id, List<AppAuthConfig> config, User user) {
        AppDO appDO = getById(id);
        checkAuth(appDO, user);
        updateAppAuth(appDO, config);
        appMapper.updateById(appDO);
        return convertDetail(appDO);
    }

    private void updateAppAuth(AppDO appDO, List<AppAuthConfig> items) {
        // delete all at first
        appAuthDOMapper.delete(new QueryWrapper<AppAuthDO>().eq("app_id", appDO.getId()));
        // insert for each item
        if (CollectionUtils.isEmpty(items)) {
            return;
        }
        items.forEach(item -> {
            AppAuthDO appAuthDO = new AppAuthDO();
            appAuthDO.setAppId(appDO.getId());
            appAuthDO.setDataId(item.getId());
            appAuthDO.setAuthType(item.getType().name());
            appAuthDO.setConfig(JSON.toJSONString(item.getRelateItems()));
            appAuthDOMapper.insert(appAuthDO);
        });
    }

    @Override
    public void online(Long id, User user) {
        AppDO appDO = getAppDO(id);
        checkAuth(appDO, user);
        appDO.setStatus(AppStatus.ONLINE.getCode());
        appDO.setUpdatedAt(new Date());
        appDO.setUpdatedBy(user.getUserName());
        updateById(appDO);
    }

    @Override
    public void offline(Long id, User user) {
        AppDO appDO = getAppDO(id);
        checkAuth(appDO, user);
        appDO.setStatus(AppStatus.OFFLINE.getCode());
        appDO.setUpdatedAt(new Date());
        appDO.setUpdatedBy(user.getUserName());
        updateById(appDO);
    }

    @Override
    public void delete(Long id, User user) {
        AppDO appDO = getAppDO(id);
        checkAuth(appDO, user);
        appDO.setStatus(AppStatus.DELETED.getCode());
        appDO.setUpdatedAt(new Date());
        appDO.setUpdatedBy(user.getUserName());
        updateById(appDO);
    }

    @Override
    public PageInfo<AppResp> pageApp(AppQueryReq appQueryReq, User user) {
        PageInfo<AppDO> appDOPageInfo = PageHelper.startPage(appQueryReq.getCurrent(), appQueryReq.getPageSize())
                                                  .doSelectPageInfo(() -> queryApp(appQueryReq));
        PageInfo<AppResp> appPageInfo = PageUtils.pageInfo2PageInfoVo(appDOPageInfo);
        appPageInfo.setList(
            appDOPageInfo.getList().stream().map(appDO -> convert(appDO, user))
                         .collect(Collectors.toList()));
        return appPageInfo;
    }

    public List<AppDO> queryApp(AppQueryReq appQueryReq) {
        QueryWrapper<AppDO> appDOQueryWrapper = new QueryWrapper<>();
        appDOQueryWrapper.lambda().ne(AppDO::getStatus, AppStatus.DELETED.getCode());
        if (StringUtils.isNotBlank(appQueryReq.getName())) {
            appDOQueryWrapper.lambda().like(AppDO::getName, appQueryReq.getName());
        }
        if (StringUtils.isNotBlank(appQueryReq.getCreatedBy())) {
            appDOQueryWrapper.lambda().eq(AppDO::getCreatedBy, appQueryReq.getCreatedBy());
        }
        return list(appDOQueryWrapper);
    }

    @Override
    public AppDetailResp getApp(Long id, User user) {
        AppDO appDO = getAppDO(id);
        Map<Long, MetricResp> metricResps = metricService.getMetrics(new MetaFilter()).stream()
                                                         .collect(Collectors.toMap(MetricResp::getId, m -> m));
        Map<Long, DimensionResp> dimensionResps = dimensionService.getDimensions(new MetaFilter()).stream().collect(
            Collectors.toMap(DimensionResp::getId, m -> m));
        checkAuth(appDO, user);
        return convertDetail(appDO, dimensionResps, metricResps);
    }

    @Override
    public AppDetailResp getApp(Long id) {
        AppDO appDO = getAppDO(id);
        return convertDetail(appDO, new HashMap<>(), new HashMap<>());
    }

    @Override
    public AppDetailResp getApp(String appId) {
        QueryWrapper<AppDO> appDOQueryWrapper = new QueryWrapper<>();
        appDOQueryWrapper.lambda().ne(AppDO::getStatus, AppStatus.DELETED.getCode()).eq(AppDO::getAppId, appId);
        AppDO appDO = appMapper.selectOne(appDOQueryWrapper);
        if (appDO == null) {
            return null;
        }
        return convertDetail(appDO);
    }

    private AppAuthConfig getAuthConfig(Long appId, Long dataId, AppAuthType type) {
        LambdaQueryWrapper<AppAuthDO> appAuthDOQueryWrapper = new LambdaQueryWrapper<>();
        appAuthDOQueryWrapper.eq(AppAuthDO::getAppId, appId)
                             .eq(AppAuthDO::getDataId, dataId)
                             .eq(AppAuthDO::getAuthType, type.name().toLowerCase(Locale.ROOT));
        AppAuthDO appAuthDO = appAuthDOMapper.selectOne(appAuthDOQueryWrapper);
        if (appAuthDO == null) {
            return null;
        }
        return JSONObject.parseObject(appAuthDO.getConfig(), AppAuthConfig.class);
    }

    // 应用权限检测
    @Override
    public boolean checkPermission(AppResp app, AppAuthType type, Long keyId) {
        if (app == null) {
            return false;
        }
        AppAuthConfig config = getAuthConfig(app.getId(), keyId, type);
        if (config == null) {
            return false;
        }
        return true;
    }

    // dataset
    private boolean checkPermission(AppAuthConfig config, DataSetResp dataSetResp) {
        return true;
    }

    // metric
    private boolean checkPermission(AppAuthConfig config, MetricResp metricResp) {
        return true;
    }

    private AppDO getAppDO(Long id) {
        AppDO appDO = getById(id);
        if (appDO == null) {
            throw new InvalidArgumentException("该应用不存在");
        }
        return appDO;
    }

    private void checkAuth(AppDO appDO, User user) {
        if (!hasAuth(appDO, user)) {
            throw new InvalidPermissionException("您不是该应用的负责人, 暂无权查看或者修改");
        }
    }

    private boolean hasAuth(AppDO appDO, User user) {
        if (appDO.getCreatedBy().equalsIgnoreCase(user.getUserName())) {
            return true;
        }
        return StringUtils.isNotBlank(appDO.getOwner()) && appDO.getOwner().contains(user.getUserName());
    }

    private AppResp convert(AppDO appDO, User user) {
        AppResp app = new AppResp();
        BeanMapper.mapper(appDO, app);
        app.setAppStatus(AppStatus.fromCode(appDO.getStatus()));
        app.setHasAdminRes(hasAuth(appDO, user));
        return app;
    }

    private AppDetailResp convertDetail(AppDO appDO) {
        return convertDetail(appDO, new HashMap<>(), new HashMap<>());
    }

    private AppDetailResp convertDetail(AppDO appDO, Map<Long, DimensionResp> dimensionMap,
                                        Map<Long, MetricResp> metricMap) {
        AppDetailResp app = new AppDetailResp();
        BeanMapper.mapper(appDO, app);
        app.setAppStatus(AppStatus.fromCode(appDO.getStatus()));
        return app;
    }

    private String getUniqueId() {
        return UUID.randomUUID().toString().replaceAll("_", "");
    }

}
