package com.bocloud.ims.repository.resource.hmc;

import com.bocloud.cmp.entity.enums.HmcPartitionType;
import com.bocloud.cmp.model.hmc.enums.VirtualAdapterTargetType;
import com.bocloud.cmp.model.hmc.enums.VirtualScsiAdapterType;
import com.bocloud.ims.entity.common.MapBuilder;
import com.bocloud.ims.entity.resource.hmc.HmdPartition;
import com.bocloud.ims.entity.resource.hmc.HmdProfile;
import com.bocloud.ims.entity.resource.hmc.HmdServer;
import com.bocloud.ims.entity.resource.hmc.HmdViosPhysicalDisk;
import com.bocloud.ims.entity.resource.hmc.HmdViosVirtualCd;
import com.bocloud.ims.entity.resource.hmc.HmdViosVirtualDisk;
import com.bocloud.ims.entity.resource.hmc.HmdVirtualScsiAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

@Repository
public abstract class HmdVirtualScsiAdapterRepository extends HmdGenericDao<HmdVirtualScsiAdapter> {

    private static final Logger logger = LoggerFactory.getLogger(HmdVirtualScsiAdapterRepository.class);

    public HmdVirtualScsiAdapterRepository(JdbcTemplate jdbcTemplate, NamedParameterJdbcTemplate npJdbcTemplate) {
        super(jdbcTemplate, npJdbcTemplate);
    }

    @Lookup protected abstract HmdServerRepository serverRepository();
    @Lookup protected abstract HmdPartitionRepository partitionRepository();
    @Lookup protected abstract HmdProfileRepository profileRepository();
    @Lookup protected abstract HmdViosPhysicalDiskRepository viosPhysicalDiskRepository();
    @Lookup protected abstract HmdViosVirtualDiskRepository viosVirtualDiskRepository();
    @Lookup protected abstract HmdViosVirtualCdRepository viosVirtualCdRepository();

    @Override
    public void extra(HmdVirtualScsiAdapter adapter) {
        Optional.of(adapter).map(HmdVirtualScsiAdapter::getTargetType).map(VirtualAdapterTargetType::valueOf).ifPresent(targetType -> {
            switch (targetType) {
                case PARTITION: {
                    Optional.of(adapter).map(HmdVirtualScsiAdapter::getTargetId).map(this.partitionRepository()::getById).ifPresent(adapter::setPartition);
                    Optional.of(adapter).map(HmdVirtualScsiAdapter::getType).map(VirtualScsiAdapterType::byTypeNullable).filter(VirtualScsiAdapterType.SERVER::equals).ifPresent(type ->
                        Optional.of(adapter).map(HmdVirtualScsiAdapter::getTargetId).ifPresent(viosId ->
                            Optional.of(adapter).map(HmdVirtualScsiAdapter::getAdapterId).ifPresent(adapterId -> {
                                adapter.setPhysicalDisks(this.viosPhysicalDiskRepository().listByViosAdapterId(viosId, adapterId));
                                adapter.setVirtualDisks(this.viosVirtualDiskRepository().listByViosAdapterId(viosId, adapterId));
                                adapter.setVirtualCds(this.viosVirtualCdRepository().listByViosAdapterId(viosId, adapterId));
                            })
                        )
                    );
                    break;
                }
                case PROFILE: {
                    Optional.of(adapter).map(HmdVirtualScsiAdapter::getTargetId).map(this.profileRepository()::getById).ifPresent(adapter::setProfile);
                    break;
                }
                default:
                    throw new IllegalArgumentException("未知的类型：" + targetType.name());
            }
        });
    }

    public List<HmdVirtualScsiAdapter> listByPartitionId(long partitionId) {
        Map<String, Object> map = MapBuilder.init()
            .put("a.target_id", partitionId)
            .put("a.type", VirtualAdapterTargetType.PARTITION.name())
            .get();
        return this.search(map);
    }

    public List<HmdVirtualScsiAdapter> listByProfileId(long profileId) {
        Map<String, Object> map = MapBuilder.init()
            .put("a.target_id", profileId)
            .put("a.type", VirtualAdapterTargetType.PROFILE.name())
            .get();
        return this.search(map);
    }

    public HmdVirtualScsiAdapter getById(long id) {
        return this.search(Collections.singletonMap("a.id", id)).stream().findFirst().orElse(null);
    }

    @Override
    public void convertInstance(final HmdVirtualScsiAdapter srcT, final HmdVirtualScsiAdapter desT) {
        AtomicReference<HmdServer> server = new AtomicReference<>();
        Optional.of(srcT).map(HmdVirtualScsiAdapter::getTargetType).map(VirtualAdapterTargetType::valueOf).ifPresent(targetType -> {
            switch (targetType) {
                case PARTITION: {
                    server.set(Optional.of(srcT)
                        .map(HmdVirtualScsiAdapter::getPartition)
                        .map(HmdPartition::getServerId)
                        .map(this.serverRepository()::getById)
                        .orElse(null)
                    );
                    break;
                }
                case PROFILE: {
                    server.set(Optional.of(srcT)
                        .map(HmdVirtualScsiAdapter::getProfile)
                        .map(HmdProfile::getPartitionId)
                        .map(this.partitionRepository()::getById)
                        .map(HmdPartition::getServerId)
                        .map(this.serverRepository()::getById)
                        .orElse(null)
                    );
                    break;
                }
                default:
                    throw new IllegalArgumentException("未知的类型：" + targetType.name());
            }
        });

        Optional<HmdPartition> partition = Optional.of(srcT).map(HmdVirtualScsiAdapter::getRemotePartition).map(HmdPartition::getUuid).map(this.partitionRepository()::getByUuid);
        partition.map(HmdPartition::getId).ifPresent(srcT::setRemotePartitionId);
        partition.ifPresent(srcT::setRemotePartition);

        if (!partition.isPresent() && server.get() != null) {
            partition = Optional.of(srcT).map(HmdVirtualScsiAdapter::getRemotePartition)
                .map(HmdPartition::getPartitionId)
                .map(partitonId -> this.partitionRepository().getByPartitionId(server.get().getId(), partitonId));
            partition.map(HmdPartition::getId).ifPresent(srcT::setRemotePartitionId);
            partition.ifPresent(srcT::setRemotePartition);
        }

        this.copy(srcT, desT);
    }

    @Override
    public void syncInstance(HmdVirtualScsiAdapter newT, HmdVirtualScsiAdapter oldT, long userId) {
        super.syncInstance(newT, oldT, userId);

        VirtualAdapterTargetType targetType = VirtualAdapterTargetType.valueOf(newT.getTargetType());
        if (targetType != VirtualAdapterTargetType.PARTITION) {
            return;
        }
        HmdPartition partition = this.partitionRepository().getById(newT.getTargetId());
        if (partition == null) {
            return;
        }
        HmcPartitionType partitionType = HmcPartitionType.valueOf(partition.getType());
        if (partitionType != HmcPartitionType.VIOS) {
            return;
        }

        Optional.of(newT).map(HmdVirtualScsiAdapter::getPhysicalDisks).ifPresent(newDisks ->
            newDisks.forEach(newDisk -> {
                Optional.of(newDisk).map(HmdViosPhysicalDisk::getName)
                    .map(diskName -> this.viosPhysicalDiskRepository().getByName(partition.getId(), diskName))
                    .ifPresent(oldDisk -> {
                        oldDisk.setAssignPartitionId(newT.getRemotePartitionId());
                        oldDisk.setAssignPartition(newT.getRemotePartition());
                        oldDisk.setAssignAdapterId(newT.getRemoteAdapterId());
                        oldDisk.setAssignTargetName(newDisk.getAssignTargetName());
                        oldDisk.setAssignTargetUniqueDeviceId(newDisk.getAssignTargetUniqueDeviceId());
                        this.viosPhysicalDiskRepository().update(oldDisk, userId);
                    });
            })
        );

        Optional.of(newT).map(HmdVirtualScsiAdapter::getVirtualDisks).ifPresent(newDisks ->
            newDisks.forEach(newDisk -> {
                Optional.of(newDisk).map(HmdViosVirtualDisk::getName)
                    .map(diskName -> this.viosVirtualDiskRepository().getByName(partition.getId(), diskName))
                    .ifPresent(oldDisk -> {
                        oldDisk.setAssignPartitionId(newT.getRemotePartitionId());
                        oldDisk.setAssignPartition(newT.getRemotePartition());
                        oldDisk.setAssignAdapterId(newT.getRemoteAdapterId());
                        oldDisk.setAssignTargetName(newDisk.getAssignTargetName());
                        oldDisk.setAssignTargetUniqueDeviceId(newDisk.getAssignTargetUniqueDeviceId());
                        this.viosVirtualDiskRepository().update(oldDisk, userId);
                    });
            })
        );

        Optional.of(newT).map(HmdVirtualScsiAdapter::getVirtualCds).ifPresent(newCds ->
            newCds.forEach(newCd -> {
                Optional.of(newCd).map(HmdViosVirtualCd::getName)
                    .map(cdName -> this.viosVirtualCdRepository().getByName(partition.getId(), cdName))
                    .ifPresent(oldDisk -> {
                        oldDisk.setAssignPartitionId(newT.getRemotePartitionId());
                        oldDisk.setAssignPartition(newT.getRemotePartition());
                        oldDisk.setAssignAdapterId(newT.getRemoteAdapterId());
                        oldDisk.setAssignTargetName(newCd.getAssignTargetName());
                        oldDisk.setAssignTargetUniqueDeviceId(newCd.getAssignTargetUniqueDeviceId());
                        this.viosVirtualCdRepository().update(oldDisk, userId);
                    });
            })
        );
    }
}
