package org.jetlinks.community.device.web;

import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.CollectionUtils;
import org.hswebframework.web.api.crud.entity.PagerResult;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.authorization.annotation.SaveAction;
import org.jetlinks.core.device.DeviceConfigKey;
import org.jetlinks.core.device.DeviceRegistry;
import org.jetlinks.community.device.entity.DeviceInstanceEntity;
import org.jetlinks.community.device.entity.DeviceProductEntity;
import org.jetlinks.community.device.enums.DeviceType;
import org.jetlinks.community.device.service.LocalDeviceInstanceService;
import org.jetlinks.community.device.service.LocalDeviceProductService;
import org.jetlinks.community.device.web.response.GatewayDeviceInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 网关设备接口
 *
 * @author zhouhao
 * @since 1.0
 */
@RestController
@RequestMapping("/device/gateway")
@Resource(id = "device-gateway", name = "网关设备管理")
public class GatewayDeviceController {

    @Autowired
    private LocalDeviceInstanceService instanceService;

    @Autowired
    private LocalDeviceProductService productService;

    @Autowired
    private DeviceRegistry registry;

    @SuppressWarnings("all")
    private Mono<List<String>> getGatewayProductList() {
        return productService
            .createQuery()
            .select(DeviceProductEntity::getId)
            .where(DeviceProductEntity::getDeviceType, DeviceType.gateway)
            .fetch()
            .map(DeviceProductEntity::getId)
            .collectList()
            .filter(CollectionUtils::isNotEmpty);
    }

    @GetMapping("/_query")
    @QueryAction
    public Mono<PagerResult<GatewayDeviceInfo>> queryGatewayDevice(QueryParamEntity param) {
        return getGatewayProductList()
            .flatMap(productIdList ->
                param.toNestQuery(q -> q.in(DeviceInstanceEntity::getProductId, productIdList))
                    .execute(instanceService::queryPager)
                    .filter(r -> r.getTotal() > 0)
                    .flatMap(result -> {
                        Map<String, DeviceInstanceEntity> mapping =
                            result.getData()
                                .stream()
                                .collect(Collectors.toMap(DeviceInstanceEntity::getId, Function.identity()));

                        //查询所有子设备
                        return instanceService.createQuery()
                            .where()
                            .in(DeviceInstanceEntity::getParentId, mapping.keySet())
                            .fetch()
                            .groupBy(DeviceInstanceEntity::getParentId)
                            .flatMap(group -> {
                                String parentId = group.key();
                                return group
                                    .collectList()
                                    .map(children -> GatewayDeviceInfo.of(mapping.get(parentId), children));
                            })
                            .collectMap(GatewayDeviceInfo::getId)//收集所有有子设备的网关设备信息
                            .defaultIfEmpty(Collections.emptyMap())
                            .flatMapMany(map -> Flux.fromIterable(mapping.values())
                                .flatMap(ins -> Mono.justOrEmpty(map.get(ins.getId()))
                                    //处理没有子设备的网关信息
                                    .switchIfEmpty(Mono.fromSupplier(() -> GatewayDeviceInfo.of(ins, Collections.emptyList())))))
                            .collectList()
                            .map(list -> PagerResult.of(result.getTotal(), list, param));
                    }))
            .defaultIfEmpty(PagerResult.empty());
    }

    @GetMapping("/{id}")
    @QueryAction
    public Mono<GatewayDeviceInfo> getGatewayInfo(@PathVariable String id) {
        return Mono.zip(
            instanceService.findById(id),
            instanceService.createQuery()
                .where()
                .is(DeviceInstanceEntity::getParentId, id)
                .fetch()
                .collectList()
                .defaultIfEmpty(Collections.emptyList()),
            GatewayDeviceInfo::of);
    }


    @PostMapping("/{gatewayId}/bind/{deviceId}")
    @SaveAction
    public Mono<GatewayDeviceInfo> bindDevice(@PathVariable String gatewayId,
                                              @PathVariable String deviceId) {
        return instanceService
            .createUpdate()
            .set(DeviceInstanceEntity::getParentId, gatewayId)
            .where(DeviceInstanceEntity::getId, deviceId)
            .execute()
            .then(registry
                .getDevice(deviceId)
                .flatMap(operator -> operator.setConfig(DeviceConfigKey.parentGatewayId, gatewayId)))
            .then(getGatewayInfo(gatewayId));
    }

    @PostMapping("/{gatewayId}/bind")
    @SaveAction
    public Mono<GatewayDeviceInfo> bindDevice(@PathVariable String gatewayId,
                                              @RequestBody Flux<String> deviceId) {

        return deviceId
            .flatMapIterable(str -> {
                if (str.startsWith("[")) {
                    return JSON.parseArray(str, String.class);
                }
                return Collections.singletonList(str);
            })
            .collectList()
            .filter(CollectionUtils::isNotEmpty)
            .flatMap(deviceIdList -> instanceService
                .createUpdate()
                .set(DeviceInstanceEntity::getParentId, gatewayId)
                .where()
                .in(DeviceInstanceEntity::getId, deviceIdList)
                .execute()
                .then(Flux
                    .fromIterable(deviceIdList)
                    .flatMap(id -> registry
                        .getDevice(id)
                        .flatMap(operator -> operator.setConfig(DeviceConfigKey.parentGatewayId, gatewayId))).then()
                )
            ).then(getGatewayInfo(gatewayId));

    }

    @PostMapping("/{gatewayId}/unbind/{deviceId}")
    @SaveAction
    public Mono<GatewayDeviceInfo> unBindDevice(@PathVariable String gatewayId,
                                                @PathVariable String deviceId) {
        return instanceService
            .createUpdate()
            .setNull(DeviceInstanceEntity::getParentId)
            .where(DeviceInstanceEntity::getId, deviceId)
            .and(DeviceInstanceEntity::getParentId, gatewayId)
            .execute()
            .filter(i -> i > 0)
            .flatMap(i -> registry
                .getDevice(deviceId)
                .flatMap(operator -> operator.removeConfig(DeviceConfigKey.parentGatewayId.getKey())))
            .then(getGatewayInfo(gatewayId));
    }
}