package com.yfd.platform.modules.storage.service;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.yfd.platform.exception.StorageSourceException;
import com.yfd.platform.exception.file.InvalidStorageSourceException;
import com.yfd.platform.modules.config.model.request.SaveStorageSourceRequest;
import com.yfd.platform.modules.config.model.request.UpdateStorageSortRequest;
import com.yfd.platform.modules.storage.context.StorageSourceContext;
import com.yfd.platform.modules.storage.convert.StorageSourceConvert;
import com.yfd.platform.modules.storage.mapper.StorageSourceMapper;
import com.yfd.platform.modules.storage.model.dto.StorageSourceAllParamDTO;
import com.yfd.platform.modules.storage.model.dto.StorageSourceDTO;
import com.yfd.platform.modules.storage.model.entity.StorageSource;
import com.yfd.platform.modules.storage.model.entity.StorageSourceConfig;
import com.yfd.platform.modules.storage.model.enums.StorageTypeEnum;
import com.yfd.platform.utils.CodeMsg;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * 存储源基本信息 Service
 *
 * @author zhengsl
 */
@Slf4j
@Service
@CacheConfig(cacheNames = "storageSource")
public class StorageSourceService {

    @Resource
    private StorageSourceMapper storageSourceMapper;

    @Resource
    private StorageSourceContext storageSourceContext;

    @Resource
    private StorageSourceConfigService storageSourceConfigService;

    @Resource
    private StorageSourceConvert storageSourceConvert;


    /**
     * 获取所有存储源列表
     *
     * @return 存储源列表
     */
    public List<StorageSource> findAllOrderByOrderNum() {
        return storageSourceMapper.findAllOrderByOrderNum();
    }


    /**
     * 获取所有已启用的存储源列表，按照存储源的排序号排序
     *
     * @return 已启用的存储源列表
     */
    public List<StorageSource> findAllEnableOrderByOrderNum() {
        return storageSourceMapper.findListByEnableOrderByOrderNum();
    }


    /**
     * 获取指定存储源设置
     *
     * @param   id
     *          存储源 ID
     *
     * @return  存储源设置
     */
    @Cacheable(key = "#id", unless = "#result == null")
    public StorageSource findById(Integer id) {
        return storageSourceMapper.selectById(id);
    }


    /**
     * 根据存储源 key 获取存储源
     *
     * @param   storageKey
     *          存储源 key
     *
     * @throws InvalidStorageSourceException   存储源不存在时, 抛出异常.
     *
     * @return  存储源信息
     */
    @Cacheable(key = "#storageKey", unless = "#result == null")
    public StorageSource findByStorageKey(String storageKey) {
        return storageSourceMapper.findByStorageKey(storageKey);
    }


    /**
     * 根据存储源 key 清除 key 的缓存
     *
     * @param   storageKey
     *          存储源 key
     */
    @CacheEvict(key = "#storageKey")
    public void clearCacheByStorageKey(String storageKey) {}


    /**
     * 根据存储源 key 获取存储源 id
     *
     * @param   storageKey
     *          存储源 key
     *
     * @return  存储源信息
     */
    public Integer findIdByKey(String storageKey) {
        return Optional.ofNullable(this.findByStorageKey(storageKey)).map(StorageSource::getId).orElse(null);
    }

    /**
     * 获取指定存储源 DTO 对象, 此对象包含详细的参数设置.
     *
     * @param   id
     *          存储源 ID
     *
     * @return  存储源 DTO
     */
    @Cacheable(key = "'dto-' + #id", unless = "#result == null")
    public StorageSourceDTO findDTOById(Integer id) {
        // 将参数列表通过反射写入到 StorageSourceAllParam 中.
        StorageSourceAllParamDTO storageSourceAllParam = new StorageSourceAllParamDTO();
        storageSourceConfigService.selectStorageConfigByStorageId(id)
                .forEach(storageSourceConfig ->
                        ReflectUtil.setFieldValue(storageSourceAllParam, storageSourceConfig.getName(), storageSourceConfig.getValue())
                );

        // 获取数据库对象，转为 dto 对象返回
        StorageSource storageSource = findById(id);
        return storageSourceConvert.entityToDTO(storageSource, storageSourceAllParam);
    }

    /**
     * 根据存储源 id 获取存储源 key
     *
     * @param   id
     *          存储源 id
     *
     * @return  存储源 key
     */
    public String findStorageKeyById(Integer id){
        return Optional.ofNullable(this.findById(id)).map(StorageSource::getKey).orElse(null);
    }


    /**
     * 根据 id 获取指定存储源的类型.
     *
     * @param   id
     *          存储源 ID
     *
     * @return  存储源对应的类型.
     */
    public StorageTypeEnum findStorageTypeById(Integer id) {
        return Optional.ofNullable(this.findById(id)).map(StorageSource::getType).orElse(null);
    }


    /**
     * 交换存储源排序
     *
     * @param   updateStorageSortRequestList
     *          更新排序的存储源 id 及排序值列表
     */
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(allEntries = true)
    public void updateStorageSort(List<UpdateStorageSortRequest> updateStorageSortRequestList) {
        for (int i = 0; i < updateStorageSortRequestList.size(); i++) {
            UpdateStorageSortRequest item = updateStorageSortRequestList.get(i);
            if (!Objects.equals(i, item.getOrderNum())) {
                log.info("变更存储源 {} 顺序号为 {}", item.getId(), i);
                storageSourceMapper.updateSetOrderNumById(i, item.getId());
            }
        }
    }

    /**
     * 判断存储源 key 是否已存在 (不读取缓存)
     *
     * @param   storageKey
     *          存储源 key
     *
     * @return  是否已存在
     */
    public boolean existByStorageKey(String storageKey) {
        return this.findByStorageKey(storageKey) != null;
    }


    /**
     * 删除指定存储源设置, 会级联删除其参数设置
     *
     * @param   id
     *          存储源 ID
     */
    @Transactional(rollbackFor = Exception.class)
    @Caching(evict = {
            @CacheEvict(key = "#id"),
            @CacheEvict(key = "'dto-' + #id"),
            @CacheEvict(key = "#result.key", condition = "#result != null")
    })
    public StorageSource deleteById(Integer id) {
        log.info("删除 id 为 {} 的存储源", id);
        StorageSource storageSource = findById(id);

        if (storageSource == null) {
            String msg = StrUtil.format("删除存储源时检测到 id 为 {} 的存储源不存在", id);
            throw new StorageSourceException(CodeMsg.STORAGE_SOURCE_NOT_FOUND, id, msg);
        }
        storageSourceMapper.deleteById(id);
        storageSourceConfigService.deleteByStorageId(id);
        storageSourceContext.destroy(id);
        return storageSource;
    }





    @Caching(evict = {
            @CacheEvict(key = "#entity.id"),
            @CacheEvict(key = "#entity.key"),
            @CacheEvict(key = "'dto-' + #entity.id")
    })
    public void updateById(StorageSource entity) {
        storageSourceMapper.updateById(entity);
    }


    /**
     * 保存或修改存储源设置，如果没有 id 则新增，有则更新，且会检测是否填写 key，如果没写，则自动将 id 设置为 key 并保存。
     *
     * @param   storageSource
     *          存储源对象
     *
     * @return  保存后对象
     */
    @Caching(evict = {
            @CacheEvict(key = "#result.id"),
            @CacheEvict(key = "'dto-' + #result.id"),
            @CacheEvict(key = "#result.key")
    })
    public StorageSource saveOrUpdate(StorageSource storageSource) {
        // 保存存储源基本信息
        if (storageSource.getId() == null) {
            storageSourceMapper.insert(storageSource);
        } else {
            // 判断是否修改了存储源别名，如果修改了则清除之前存储源别名的缓存。
            StorageSource originStorageSource = storageSourceMapper.selectById(storageSource.getId());
            if (!StrUtil.equals(originStorageSource.getKey(), storageSource.getKey())) {
                this.clearCacheByStorageKey(originStorageSource.getKey());
            }
            storageSourceMapper.updateById(storageSource);
        }

        // 如果没输入存储源 key, 则自动将 id 设置为 key
        if (StrUtil.isEmpty(storageSource.getKey()) && !StrUtil.equals(storageSource.getId().toString(), storageSource.getKey())) {
            storageSource.setKey(Convert.toStr(storageSource.getId()));
            storageSourceMapper.updateById(storageSource);
        }
        return storageSource;
    }

    /**
     * 保存存储源基本信息及其对应的参数设置
     *
     * @param   saveStorageSourceRequest
     *          存储源 DTO 对象
     */
    @Transactional(rollbackFor = Exception.class)
    public Integer saveStorageSource(SaveStorageSourceRequest saveStorageSourceRequest) {
        log.info("尝试保存存储源, id: {}, name: {}, key: {}, type: {}",
                saveStorageSourceRequest.getId(), saveStorageSourceRequest.getName(),
                saveStorageSourceRequest.getKey(), saveStorageSourceRequest.getType().getDescription());

        // 转换为存储源 entity 对象
        StorageSource storageSource = storageSourceConvert.saveRequestToEntity(saveStorageSourceRequest);

        // 保存或更新存储源
        StorageSource dbSaveResult = this.saveOrUpdate(storageSource);

        log.info("保存存储源成功, id: {}, name: {}, key: {}, type: {}",
                dbSaveResult.getId(), dbSaveResult.getName(),
                dbSaveResult.getKey(), dbSaveResult.getType().getDescription());

        // 存储源 ID
        Integer storageId = dbSaveResult.getId();

        // 保存存储源参数
        List<StorageSourceConfig> storageSourceConfigList =
                storageSourceConfigService.toStorageSourceConfigList(storageId,
                        dbSaveResult.getType(),
                        saveStorageSourceRequest.getStorageSourceAllParam());
        storageSourceConfigService.saveBatch(storageId, storageSourceConfigList);
        log.info("保存存储源参数成功，尝试根据参数初始化存储源, id: {}, name: {}, config size: {}",
                dbSaveResult.getId(), dbSaveResult.getName(), storageSourceConfigList.size());

        // 初始化并检查是否可用
        storageSourceContext.init(storageSource);
        log.info("根据参数初始化存储源成功, id: {}, name: {}, config size: {}",
                dbSaveResult.getId(), dbSaveResult.getName(), storageSourceConfigList.size());

        return storageId;
    }

}
