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.VirtualNetworkAdapterType;
import com.bocloud.ims.entity.common.MapBuilder;
import com.bocloud.ims.entity.resource.hmc.*;
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.stream.Collectors;

@Repository
public abstract class HmdPartitionRepository extends HmdGenericDao<HmdPartition> {

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

    @Lookup
    protected abstract HmdServerRepository serverRepository();

    @Lookup
    protected abstract HmdVirtualIoAdapterRepository virtualIoAdapterRepository();

    @Lookup
    protected abstract HmdVirtualNetworkAdapterRepository virtualNetworkAdapterRepository();

    @Lookup
    protected abstract HmdVirtualScsiAdapterRepository virtualScsiAdapterRepository();

    @Lookup
    protected abstract HmdVirtualFabricAdapterRepository virtualFabricAdapterRepository();

    @Lookup
    protected abstract HmdProfileRepository profileRepository();

    @Lookup
    protected abstract HmdViosPhysicalDiskRepository viosPhysicalDiskRepository();

    @Lookup
    protected abstract HmdViosVirtualDiskRepository viosVirtualDiskRepository();

    @Lookup
    protected abstract HmdViosVirtualCdRepository viosVirtualCdRepository();

    @Lookup
    protected abstract HmdViosVolumeGroupRepository viosVolumeGroupRepository();

    @Lookup
    protected abstract HmdViosPhysicalEthernetAdapterRepository viosPhysicalEthernetAdapterRepository();

    @Lookup
    protected abstract HmdViosPhysicalFabricAdapterRepository viosPhysicalFabricAdapterRepository();

    @Lookup
    protected abstract HmdViosSeaRepository viosSeaRepository();

    @Lookup
    protected abstract HmdServerSharedProcessorPoolRepository serverSharedProcessorPoolRepository();

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

    @Override
    protected String getListSql() {
        return "select a.* from hmd_partition a " + "inner join hmd_server b on a.server_id = b.id "
                + "left  join hmd_server_shared_processor_pool c on a.pool_id = c.id " + "where 1=1 ";
    }

    @Override
    protected String getCountSql() {
        return "select count(1) from hmd_partition a " + "join hmd_server b on a.server_id = b.id " + "where 1=1 ";
    }

    @Override
    public void extra(HmdPartition partition) {
        Optional.of(partition).map(HmdPartition::getId).map(this.profileRepository()::listByPartitionId)
                .ifPresent(partition::setProfiles);
        Optional.of(partition).map(HmdPartition::getId).map(this.virtualIoAdapterRepository()::listByPartitionId)
                .ifPresent(partition::setIoAdapters);
        Optional.of(partition).map(HmdPartition::getId)
                .map(partitionId -> this.virtualNetworkAdapterRepository().listByPartitionId(partitionId).stream()
                        .filter(adapter -> adapter.getType().equals(VirtualNetworkAdapterType.CLIENT.name()))
                        .collect(Collectors.toList()))
                .ifPresent(partition::setClientNetworkAdapters);
        Optional.of(partition).map(HmdPartition::getId)
                .map(partitionId -> this.virtualNetworkAdapterRepository().listByPartitionId(partitionId).stream()
                        .filter(adapter -> adapter.getType().equals(VirtualNetworkAdapterType.TRUNK.name()))
                        .collect(Collectors.toList()))
                .ifPresent(partition::setTrunkNetworkAdapters);
        Optional.of(partition).map(HmdPartition::getId).map(this.virtualScsiAdapterRepository()::listByPartitionId)
                .ifPresent(partition::setScsiAdapters);
        Optional.of(partition).map(HmdPartition::getId).map(this.virtualFabricAdapterRepository()::listByPartitionId)
                .ifPresent(partition::setFabricAdapters);
        Optional.of(partition).map(HmdPartition::getId).map(this.viosPhysicalDiskRepository()::listByViosId)
                .ifPresent(partition::setPhysicalDisks);
        Optional.of(partition).map(HmdPartition::getId).map(this.viosVirtualCdRepository()::listByViosId)
                .ifPresent(partition::setVirtualCds);
        Optional.of(partition).map(HmdPartition::getId).map(this.viosVolumeGroupRepository()::listByViosId)
                .ifPresent(partition::setVolumeGroups);
        Optional.of(partition).map(HmdPartition::getId).map(this.viosSeaRepository()::listByViosId)
                .ifPresent(partition::setSeas);
        Optional.of(partition).map(HmdPartition::getId).map(this.viosPhysicalEthernetAdapterRepository()::listByViosId)
                .ifPresent(partition::setPhysicalEthernetAdapters);
        Optional.of(partition).map(HmdPartition::getId).map(this.viosPhysicalFabricAdapterRepository()::listByViosId)
                .ifPresent(partition::setPhysicalFabricAdapters);
        Optional.of(partition).map(HmdPartition::getServerId).map(this.serverRepository()::getById)
                .ifPresent(partition::setServer);
        Optional.of(partition).map(HmdPartition::getPoolId).map(this.serverSharedProcessorPoolRepository()::getById)
                .ifPresent(partition::setProcessorPool);
    }

    public List<HmdPartition> listByServerId(long serverId) {
        return this.search(Collections.singletonMap("a.server_id", serverId));
    }

    public List<HmdPartition> search(long vendorId, Long serverId, HmcPartitionType partitionType, String stauts) {
        Map<String, Object> map = MapBuilder.init().put("b.vendor_id", vendorId).put("a.server_id", serverId)
                .put("a.type", partitionType.getType()).put("a.status", stauts).get();
        return this.search(map);
    }

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

    public HmdPartition getByUuid(String uuid) {
        return this.search(Collections.singletonMap("a.uuid", uuid)).stream().findFirst().orElse(null);
    }

    public HmdPartition getByPartitionId(long serverId, int partitionId) {
        Map<String, Object> params = MapBuilder.init().put("a.server_id", serverId).put("a.partition_id", partitionId)
                .get();
        return this.search(params).stream().findFirst().orElse(null);
    }

    public List<HmdPartition> listByTopProcessor(long vendorId, String topType, int limitNum) {
        StringBuilder sb = new StringBuilder();

        sb.append("select a.name, a.processor/c.installed_system_processor_units value ");
        sb.append("  from hmc_partition a ");
        sb.append(" inner join hmc_server b on b.id = a.server_id ");
        sb.append(" inner join hmc_system_processor_config c on c.server_id = b.id ");
        sb.append(" where a.is_deleted = 0 ");
        sb.append("   and b.is_deleted = 0 ");
        sb.append("   and c.installed_system_processor_units > 0 ");
        sb.append("   and b.vendor_id = :vendorId ");
        sb.append("  order by value ");
        if ("MAX".equals(topType)) {
            sb.append("desc");
        } else {
            sb.append("asc");
        }
        sb.append("  limit ");
        sb.append(limitNum);

        String sql = sb.toString();

        Map<String, Object> params = Collections.singletonMap("vendorId", vendorId);
        return this.queryForList(sql, params, HmdPartition.class).stream().map(o -> (HmdPartition) o)
                .collect(Collectors.toList());
    }

    public List<HmdPartition> listByTopMemory(long vendorId, String topType, int limitNum) {
        StringBuilder sb = new StringBuilder();

        sb.append("select a.name, a.memory/c.installed_system_memory value ");
        sb.append("  from hmc_partition a ");
        sb.append(" inner join hmc_server b on b.id = a.server_id ");
        sb.append(" inner join hmc_system_memory_config c on c.server_id = b.id ");
        sb.append(" where a.is_deleted = 0 ");
        sb.append("   and b.is_deleted = 0 ");
        sb.append("   and c.installed_system_memory > 0 ");
        sb.append("   and b.vendor_id = :vendorId ");
        sb.append("  order by value ");
        if ("MAX".equals(topType)) {
            sb.append("desc");
        } else {
            sb.append("asc");
        }
        sb.append("  limit ");
        sb.append(limitNum);

        String sql = sb.toString();

        Map<String, Object> params = Collections.singletonMap("vendorId", vendorId);

        return this.queryForList(sql, params, HmdPartition.class).stream().map(o -> (HmdPartition) o)
                .collect(Collectors.toList());
    }

    public HmdPartition getServicePartitionByServerId(long serverId) {
        Map<String, Object> params = MapBuilder.init().put("a.serverId", serverId).put("a.service_partition", 1).get();
        return this.search(params).stream().findFirst().orElse(null);
    }

    @Override
    public void clear(long id) {
        this.virtualIoAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualIoAdapter::getId)
                .forEach(this.virtualIoAdapterRepository()::clear);
        this.virtualNetworkAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualNetworkAdapter::getId)
                .forEach(this.virtualNetworkAdapterRepository()::clear);
        this.virtualScsiAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualScsiAdapter::getId)
                .forEach(this.virtualScsiAdapterRepository()::clear);
        this.virtualFabricAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualFabricAdapter::getId)
                .forEach(this.virtualFabricAdapterRepository()::clear);

        this.profileRepository().listByPartitionId(id).stream().map(HmdProfile::getId)
                .forEach(this.profileRepository()::clear);

        this.viosPhysicalDiskRepository().listByViosId(id).stream().map(HmdViosPhysicalDisk::getId)
                .forEach(this.viosPhysicalDiskRepository()::clear);
        this.viosVirtualDiskRepository().listByViosId(id).stream().map(HmdViosVirtualDisk::getId)
                .forEach(this.viosVirtualDiskRepository()::clear);
        this.viosVirtualCdRepository().listByViosId(id).stream().map(HmdViosVirtualCd::getId)
                .forEach(this.viosVirtualCdRepository()::clear);
        this.viosVolumeGroupRepository().listByViosId(id).stream().map(HmdViosVolumeGroup::getId)
                .forEach(this.viosVolumeGroupRepository()::clear);
        this.viosPhysicalEthernetAdapterRepository().listByViosId(id).stream()
                .map(HmdViosPhysicalEthernetAdapter::getId)
                .forEach(this.viosPhysicalEthernetAdapterRepository()::clear);
        this.viosPhysicalFabricAdapterRepository().listByViosId(id).stream().map(HmdViosPhysicalFabricAdapter::getId)
                .forEach(this.viosPhysicalFabricAdapterRepository()::clear);
        this.viosSeaRepository().listByViosId(id).stream().map(HmdViosSea::getId)
                .forEach(this.viosSeaRepository()::clear);

        this.delete(id);
    }

    @Override
    public void startAllPending(long id) {
        this.virtualIoAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualIoAdapter::getId)
                .forEach(this.virtualIoAdapterRepository()::startAllPending);
        this.virtualNetworkAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualNetworkAdapter::getId)
                .forEach(this.virtualNetworkAdapterRepository()::startAllPending);
        this.virtualScsiAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualScsiAdapter::getId)
                .forEach(this.virtualScsiAdapterRepository()::startAllPending);
        this.virtualFabricAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualFabricAdapter::getId)
                .forEach(this.virtualFabricAdapterRepository()::startAllPending);

        this.profileRepository().listByPartitionId(id).stream().map(HmdProfile::getId)
                .forEach(this.profileRepository()::startAllPending);

        this.viosPhysicalDiskRepository().listByViosId(id).stream().map(HmdViosPhysicalDisk::getId)
                .forEach(this.viosPhysicalDiskRepository()::startAllPending);
        this.viosVirtualDiskRepository().listByViosId(id).stream().map(HmdViosVirtualDisk::getId)
                .forEach(this.viosVirtualDiskRepository()::startAllPending);
        this.viosVirtualCdRepository().listByViosId(id).stream().map(HmdViosVirtualCd::getId)
                .forEach(this.viosVirtualCdRepository()::startAllPending);
        this.viosVolumeGroupRepository().listByViosId(id).stream().map(HmdViosVolumeGroup::getId)
                .forEach(this.viosVolumeGroupRepository()::startAllPending);
        this.viosPhysicalEthernetAdapterRepository().listByViosId(id).stream()
                .map(HmdViosPhysicalEthernetAdapter::getId)
                .forEach(this.viosPhysicalEthernetAdapterRepository()::startAllPending);
        this.viosPhysicalFabricAdapterRepository().listByViosId(id).stream().map(HmdViosPhysicalFabricAdapter::getId)
                .forEach(this.viosPhysicalFabricAdapterRepository()::startAllPending);
        this.viosSeaRepository().listByViosId(id).stream().map(HmdViosSea::getId)
                .forEach(this.viosSeaRepository()::startAllPending);

        this.startPending(id);
    }

    @Override
    public void stopAllPending(long id) {
        this.virtualIoAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualIoAdapter::getId)
                .forEach(this.virtualIoAdapterRepository()::stopAllPending);
        this.virtualNetworkAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualNetworkAdapter::getId)
                .forEach(this.virtualNetworkAdapterRepository()::stopAllPending);
        this.virtualScsiAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualScsiAdapter::getId)
                .forEach(this.virtualScsiAdapterRepository()::stopAllPending);
        this.virtualFabricAdapterRepository().listByPartitionId(id).stream().map(HmdVirtualFabricAdapter::getId)
                .forEach(this.virtualFabricAdapterRepository()::stopAllPending);

        this.profileRepository().listByPartitionId(id).stream().map(HmdProfile::getId)
                .forEach(this.profileRepository()::stopAllPending);

        this.viosPhysicalDiskRepository().listByViosId(id).stream().map(HmdViosPhysicalDisk::getId)
                .forEach(this.viosPhysicalDiskRepository()::stopAllPending);
        this.viosVirtualDiskRepository().listByViosId(id).stream().map(HmdViosVirtualDisk::getId)
                .forEach(this.viosVirtualDiskRepository()::stopAllPending);
        this.viosVirtualCdRepository().listByViosId(id).stream().map(HmdViosVirtualCd::getId)
                .forEach(this.viosVirtualCdRepository()::stopAllPending);
        this.viosVolumeGroupRepository().listByViosId(id).stream().map(HmdViosVolumeGroup::getId)
                .forEach(this.viosVolumeGroupRepository()::stopAllPending);
        this.viosPhysicalEthernetAdapterRepository().listByViosId(id).stream()
                .map(HmdViosPhysicalEthernetAdapter::getId)
                .forEach(this.viosPhysicalEthernetAdapterRepository()::stopAllPending);
        this.viosPhysicalFabricAdapterRepository().listByViosId(id).stream().map(HmdViosPhysicalFabricAdapter::getId)
                .forEach(this.viosPhysicalFabricAdapterRepository()::stopAllPending);
        this.viosSeaRepository().listByViosId(id).stream().map(HmdViosSea::getId)
                .forEach(this.viosSeaRepository()::stopAllPending);

        this.startAllPending(id);
    }

    @Override
    public void convertInstance(final HmdPartition srcT, final HmdPartition desT) {
        Optional<HmdServerSharedProcessorPool> processorPool = Optional.of(srcT).map(HmdPartition::getProcessorPool)
                .map(HmdServerSharedProcessorPool::getUuid).map(this.serverSharedProcessorPoolRepository()::getByUuid);
        processorPool.map(HmdServerSharedProcessorPool::getId).ifPresent(srcT::setPoolId);
        processorPool.ifPresent(srcT::setProcessorPool);

        this.copy(srcT, desT);
    }

    @Override
    public void syncInstance(HmdPartition newT, HmdPartition oldT, long userId) {
        super.syncInstance(newT, oldT, userId);
        Optional.of(newT).map(HmdPartition::getProfiles).ifPresent(newProfiles -> {
            newProfiles.forEach(profile -> {
                profile.setPartitionId(newT.getId());
                profile.setPartition(newT);
            });
            List<HmdProfile> oldProfiles = this.profileRepository().listByPartitionId(newT.getId());
            this.profileRepository().syncList(newProfiles, oldProfiles, userId);
        });
        Optional.of(newT).map(HmdPartition::getIoAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setTargetId(newT.getId());
                adapter.setTargetType(VirtualAdapterTargetType.PARTITION.name());
                adapter.setPartition(newT);
            });
            List<HmdVirtualIoAdapter> oldAdapters = this.virtualIoAdapterRepository().listByPartitionId(newT.getId());
            this.virtualIoAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        Optional.of(newT).map(HmdPartition::getClientNetworkAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setTargetId(newT.getId());
                adapter.setTargetType(VirtualAdapterTargetType.PARTITION.name());
                adapter.setPartition(newT);
            });
            List<HmdVirtualNetworkAdapter> oldAdapters = this.virtualNetworkAdapterRepository()
                    .listByPartitionId(newT.getId()).stream()
                    .filter(adapter -> adapter.getType().equals(VirtualNetworkAdapterType.CLIENT.name()))
                    .collect(Collectors.toList());
            this.virtualNetworkAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        Optional.of(newT).map(HmdPartition::getTrunkNetworkAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setTargetId(newT.getId());
                adapter.setTargetType(VirtualAdapterTargetType.PARTITION.name());
                adapter.setPartition(newT);
            });
            List<HmdVirtualNetworkAdapter> oldAdapters = this.virtualNetworkAdapterRepository()
                    .listByPartitionId(newT.getId()).stream()
                    .filter(adapter -> adapter.getType().equals(VirtualNetworkAdapterType.TRUNK.name()))
                    .collect(Collectors.toList());
            this.virtualNetworkAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        Optional.of(newT).map(HmdPartition::getScsiAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setTargetId(newT.getId());
                adapter.setTargetType(VirtualAdapterTargetType.PARTITION.name());
                adapter.setPartition(newT);
            });
            List<HmdVirtualScsiAdapter> oldAdapters = this.virtualScsiAdapterRepository()
                    .listByPartitionId(newT.getId());
            this.virtualScsiAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        Optional.of(newT).map(HmdPartition::getFabricAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setTargetId(newT.getId());
                adapter.setTargetType(VirtualAdapterTargetType.PARTITION.name());
                adapter.setPartition(newT);
            });
            List<HmdVirtualFabricAdapter> oldAdapters = this.virtualFabricAdapterRepository()
                    .listByPartitionId(newT.getId());
            this.virtualFabricAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        Optional.of(newT).map(HmdPartition::getPhysicalDisks).ifPresent(newDisks -> {
            newDisks.forEach(disk -> {
                disk.setViosId(newT.getId());
                disk.setVios(newT);
            });
            List<HmdViosPhysicalDisk> oldDisks = this.viosPhysicalDiskRepository().listByViosId(newT.getId());
            this.viosPhysicalDiskRepository().syncList(newDisks, oldDisks, userId);
        });
        Optional.of(newT).map(HmdPartition::getVirtualCds).ifPresent(newCds -> {
            newCds.forEach(cd -> {
                cd.setViosId(newT.getId());
                cd.setVios(newT);
            });
            List<HmdViosVirtualCd> oldCds = this.viosVirtualCdRepository().listByViosId(newT.getId());
            this.viosVirtualCdRepository().syncList(newCds, oldCds, userId);
        });
        Optional.of(newT).map(HmdPartition::getVolumeGroups).ifPresent(newVgs -> {
            newVgs.forEach(vg -> {
                vg.setViosId(newT.getId());
                vg.setVios(newT);
            });
            List<HmdViosVolumeGroup> oldVgs = this.viosVolumeGroupRepository().listByViosId(newT.getId());
            this.viosVolumeGroupRepository().syncList(newVgs, oldVgs, userId);
        });
        Optional.of(newT).map(HmdPartition::getSeas).ifPresent(newSeas -> {
            newSeas.forEach(sea -> {
                sea.setViosId(newT.getId());
                sea.setVios(newT);
            });
            List<HmdViosSea> oldSeas = this.viosSeaRepository().listByViosId(newT.getId());
            this.viosSeaRepository().syncList(newSeas, oldSeas, userId);
        });
        Optional.of(newT).map(HmdPartition::getPhysicalEthernetAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setViosId(newT.getId());
                adapter.setVios(newT);
            });
            List<HmdViosPhysicalEthernetAdapter> oldAdapters = this.viosPhysicalEthernetAdapterRepository()
                    .listByViosId(newT.getId());
            this.viosPhysicalEthernetAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        Optional.of(newT).map(HmdPartition::getPhysicalFabricAdapters).ifPresent(newAdapters -> {
            newAdapters.forEach(adapter -> {
                adapter.setViosId(newT.getId());
                adapter.setVios(newT);
            });
            List<HmdViosPhysicalFabricAdapter> oldAdapters = this.viosPhysicalFabricAdapterRepository()
                    .listByViosId(newT.getId());
            this.viosPhysicalFabricAdapterRepository().syncList(newAdapters, oldAdapters, userId);
        });
        // TODO: 2020/10/28 associatedProfile
    }
}
