package org.iotp.server.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import org.iotp.infomgt.dao.asset.AssetSearchQuery;
import org.iotp.infomgt.dao.exception.IncorrectParameterException;
import org.iotp.infomgt.dao.model.ModelConstants;
import org.iotp.infomgt.data.Asset;
import org.iotp.infomgt.data.Customer;
import org.iotp.infomgt.data.TenantAssetType;
import org.iotp.infomgt.data.id.AssetId;
import org.iotp.infomgt.data.id.CustomerId;
import org.iotp.infomgt.data.id.TenantId;
import org.iotp.infomgt.data.page.TextPageData;
import org.iotp.infomgt.data.page.TextPageLink;
import org.iotp.infomgt.data.security.AssetCredentials;
import org.iotp.server.exception.IoTPException;
import org.iotp.server.msghub.ThingsMetaKafkaTopics;
import org.iotp.server.service.security.model.SecurityUser;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonObject;

@RestController
@RequestMapping("/api")
public class AssetController extends BaseController {

  @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET)
  @ResponseBody
  public Asset getAssetById(@PathVariable("assetId") String strAssetId) throws IoTPException {
    checkParameter("assetId", strAssetId);
    try {
      AssetId assetId = new AssetId(toUUID(strAssetId));
      return checkAssetId(assetId);
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/asset", method = RequestMethod.POST)
  @ResponseBody
  public Asset saveAsset(@RequestBody Asset asset) throws IoTPException {
    try {
      asset.setTenantId(getCurrentUser().getTenantId());
      return checkNotNull(assetService.saveAsset(asset));
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE)
  @ResponseStatus(value = HttpStatus.OK)
  public void deleteAsset(@PathVariable("assetId") String strAssetId) throws IoTPException {
    checkParameter("assetId", strAssetId);
    try {
      AssetId assetId = new AssetId(toUUID(strAssetId));
      checkAssetId(assetId);
      assetService.deleteAsset(assetId);
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST)
  @ResponseBody
  public Asset assignAssetToCustomer(@PathVariable("customerId") String strCustomerId,
      @PathVariable("assetId") String strAssetId) throws IoTPException {
    checkParameter("customerId", strCustomerId);
    checkParameter("assetId", strAssetId);
    try {
      CustomerId customerId = new CustomerId(toUUID(strCustomerId));
      checkCustomerId(customerId);

      AssetId assetId = new AssetId(toUUID(strAssetId));
      checkAssetId(assetId);

      return checkNotNull(assetService.assignAssetToCustomer(assetId, customerId));
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE)
  @ResponseBody
  public Asset unassignAssetFromCustomer(@PathVariable("assetId") String strAssetId) throws IoTPException {
    checkParameter("assetId", strAssetId);
    try {
      AssetId assetId = new AssetId(toUUID(strAssetId));
      Asset asset = checkAssetId(assetId);
      if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
        throw new IncorrectParameterException("Asset isn't assigned to any customer!");
      }
      return checkNotNull(assetService.unassignAssetFromCustomer(assetId));
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST)
  @ResponseBody
  public Asset assignAssetToPublicCustomer(@PathVariable("assetId") String strAssetId) throws IoTPException {
    checkParameter("assetId", strAssetId);
    try {
      AssetId assetId = new AssetId(toUUID(strAssetId));
      Asset asset = checkAssetId(assetId);
      Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId());
      return checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId()));
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  @RequestMapping(value = "/asset/{assetId}/credentials", method = RequestMethod.GET)
  @ResponseBody
  public AssetCredentials getAssetCredentialsByAssetId(@PathVariable("assetId") String strAssetId)
      throws IoTPException {
    checkParameter("assetId", strAssetId);
    try {
      AssetId assetId = new AssetId(toUUID(strAssetId));
      checkAssetId(assetId);
      return checkNotNull(assetCredentialsService.findAssetCredentialsByAssetId(assetId));
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/asset/credentials", method = RequestMethod.POST)
  @ResponseBody
  public AssetCredentials saveAssetCredentials(@RequestBody AssetCredentials assetCredentials) throws IoTPException {
    checkNotNull(assetCredentials);
    try {
      checkAssetId(assetCredentials.getAssetId());
      AssetCredentials result = checkNotNull(assetCredentialsService.updateAssetCredentials(assetCredentials));
      // actorService.onCredentialsUpdate(getCurrentUser().getTenantId(),
      // assetCredentials.getAssetId());
      JsonObject json = new JsonObject();
      json.addProperty(ThingsMetaKafkaTopics.TENANT_ID, getCurrentUser().getTenantId().toString());
      json.addProperty(ThingsMetaKafkaTopics.ASSET_ID, assetCredentials.getAssetId().toString());
      json.addProperty(ThingsMetaKafkaTopics.EVENT, ThingsMetaKafkaTopics.EVENT_CREDENTIALS_UPDATE);
      msgProducer.send(ThingsMetaKafkaTopics.METADATA_ASSET_TOPIC, assetCredentials.getAssetId().toString(), json.toString());
      return result;
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/tenant/assets", params = { "limit" }, method = RequestMethod.GET)
  @ResponseBody
  public TextPageData<Asset> getTenantAssets(@RequestParam int limit, @RequestParam(required = false) String type,
      @RequestParam(required = false) String textSearch, @RequestParam(required = false) String idOffset,
      @RequestParam(required = false) String textOffset) throws IoTPException {
    try {
      TenantId tenantId = getCurrentUser().getTenantId();
      TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
      if (type != null && type.trim().length() > 0) {
        return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
      } else {
        return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
      }
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  @RequestMapping(value = "/tenant/assets", params = { "assetName" }, method = RequestMethod.GET)
  @ResponseBody
  public Asset getTenantAsset(@RequestParam String assetName) throws IoTPException {
    try {
      TenantId tenantId = getCurrentUser().getTenantId();
      return checkNotNull(assetService.findAssetByTenantIdAndName(tenantId, assetName));
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  @RequestMapping(value = "/customer/{customerId}/assets", params = { "limit" }, method = RequestMethod.GET)
  @ResponseBody
  public TextPageData<Asset> getCustomerAssets(@PathVariable("customerId") String strCustomerId,
      @RequestParam int limit, @RequestParam(required = false) String type,
      @RequestParam(required = false) String textSearch, @RequestParam(required = false) String idOffset,
      @RequestParam(required = false) String textOffset) throws IoTPException {
    checkParameter("customerId", strCustomerId);
    try {
      TenantId tenantId = getCurrentUser().getTenantId();
      CustomerId customerId = new CustomerId(toUUID(strCustomerId));
      checkCustomerId(customerId);
      TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
      if (type != null && type.trim().length() > 0) {
        return checkNotNull(
            assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
      } else {
        return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
      }
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  @RequestMapping(value = "/assets", params = { "assetIds" }, method = RequestMethod.GET)
  @ResponseBody
  public List<Asset> getAssetsByIds(@RequestParam("assetIds") String[] strAssetIds) throws IoTPException {
    checkArrayParameter("assetIds", strAssetIds);
    try {
      SecurityUser user = getCurrentUser();
      TenantId tenantId = user.getTenantId();
      CustomerId customerId = user.getCustomerId();
      List<AssetId> assetIds = new ArrayList<>();
      for (String strAssetId : strAssetIds) {
        assetIds.add(new AssetId(toUUID(strAssetId)));
      }
      ListenableFuture<List<Asset>> assets;
      if (customerId == null || customerId.isNullUid()) {
        assets = assetService.findAssetsByTenantIdAndIdsAsync(tenantId, assetIds);
      } else {
        assets = assetService.findAssetsByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, assetIds);
      }
      return checkNotNull(assets.get());
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  @RequestMapping(value = "/assets", method = RequestMethod.POST)
  @ResponseBody
  public List<Asset> findByQuery(@RequestBody AssetSearchQuery query) throws IoTPException {
    checkNotNull(query);
    checkNotNull(query.getParameters());
    checkNotNull(query.getAssetTypes());
    checkEntityId(query.getParameters().getEntityId());
    try {
      List<Asset> assets = checkNotNull(assetService.findAssetsByQuery(query).get());
      assets = assets.stream().filter(asset -> {
        try {
          checkAsset(asset);
          return true;
        } catch (IoTPException e) {
          return false;
        }
      }).collect(Collectors.toList());
      return assets;
    } catch (Exception e) {
      throw handleException(e);
    }
  }

  @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  @RequestMapping(value = "/asset/types", method = RequestMethod.GET)
  @ResponseBody
  public List<TenantAssetType> getAssetTypes() throws IoTPException {
    try {
      SecurityUser user = getCurrentUser();
      TenantId tenantId = user.getTenantId();
      ListenableFuture<List<TenantAssetType>> assetTypes = assetService.findAssetTypesByTenantId(tenantId);
      return checkNotNull(assetTypes.get());
    } catch (Exception e) {
      throw handleException(e);
    }
  }
}