package com.airlock.iam.admin.application.service.license;

import com.airlock.iam.admin.application.service.activation.ActivationStateWatcher;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.misc.adapt.Adapter;
import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.elog.api.LogTypes;
import com.airlock.iam.license.application.service.management.LicenseUtils;
import com.airlock.iam.license.application.service.verification.store.LicenseUserCountingUserStore;
import com.airlock.iam.license.domain.model.verification.input.License;
import com.airlock.iam.license.domain.model.verification.warnings.LicenseWarning;
import com.airlock.iam.license.domain.model.verification.warnings.UserCountWarning;
import com.airlock.iam.plugin.framework.application.service.license.LicenseManager;
import com.airlock.iam.system.property.IamSystemProperties;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class LicenseService {
   private static final ELogger LOG = ELoggerFactory.getLogger(LicenseService.class);
   private final Path activeLicenseFile;
   private final LicenseManager activeLicenseManager;
   private final DetachedLicenseValidator detachedLicenseValidator;
   private final LicenseDeployer licenseDeployer;
   private final UserStore userStore;

   public LicenseService(
      Path activeLicenseFile,
      LicenseManager activeLicenseManager,
      DetachedLicenseValidator detachedLicenseValidator,
      LicenseDeployer licenseDeployer,
      UserStore userStore
   ) {
      this.activeLicenseFile = Objects.requireNonNull(activeLicenseFile);
      this.activeLicenseManager = Objects.requireNonNull(activeLicenseManager);
      this.detachedLicenseValidator = Objects.requireNonNull(detachedLicenseValidator);
      this.licenseDeployer = Objects.requireNonNull(licenseDeployer);
      this.userStore = userStore;
   }

   public ActivationStateWatcher.ActivationResult activate(String license) {
      ValidationResult result = this.validate(license);
      if (result.isValid()) {
         return this.deployLicense(license);
      }

      LOG.fatal(LogTypes.LICENSE_ERROR, "License is invalid.");
      throw new ViolationException("do not activate an invalid license.");
   }

   public ValidationResult validate(String license) {
      return this.detachedLicenseValidator.validate(license);
   }

   public Optional<License> getActiveLicense() {
      License license = this.activeLicenseManager.getLicense();
      return License.EMPTY_LICENSE.equals(license) ? Optional.empty() : Optional.of(license);
   }

   public Optional<String> getActiveLicenseText() {
      if (Files.notExists(this.activeLicenseFile)) {
         return Optional.empty();
      }

      try {
         return Optional.of(Files.readString(this.activeLicenseFile, StandardCharsets.UTF_8));
      } catch (IOException e) {
         throw new FailureException("failed to load license text", e);
      }
   }

   public List<LicenseWarning> getLicenseWarnings(String license) {
      ValidationResult validationResult = this.detachedLicenseValidator.validate(license);
      if (!validationResult.isValid()) {
         throw new ViolationException("refusing to return license warnings for invalid license");
      } else {
         return this.detachedLicenseValidator.getLicenseWarnings(license, this.getUserCounters());
      }
   }

   public List<LicenseWarning> getActiveLicenseWarnings() {
      List<LicenseWarning> licenseWarnings = this.activeLicenseManager.collectLicenseWarnings(this.getUserCounters());
      logUserCountWarnings(licenseWarnings);
      return licenseWarnings;
   }

   public void logAnalyticsSetting() {
      LicenseUtils.writeAnalyticsStatusToLog(LOG, IamSystemProperties.hasDisableAnalyticsOverride(), this.activeLicenseFile, "");
   }

   private static void logUserCountWarnings(List<LicenseWarning> licenseWarnings) {
      licenseWarnings.stream().filter(UserCountWarning.class::isInstance).forEach(LicenseService::logUserCountWarning);
   }

   private static void logUserCountWarning(LicenseWarning warning) {
      switch (warning.getSeverity()) {
         case HIGH:
            LOG.error(LogTypes.LICENSE_ERROR, warning.getText());
            break;
         case NORMAL:
         default:
            LOG.warn(LogTypes.LICENSE_ERROR, warning.getText());
      }
   }

   public Optional<Integer> getCurrentUsers() {
      return this.getUserCounters()
         .stream()
         .map(LicenseService::tryCountLicenseRelevantUsers)
         .flatMap(Optional::stream)
         .filter(it -> it > 0)
         .max(Integer::compareTo);
   }

   private static Optional<Integer> tryCountLicenseRelevantUsers(LicenseUserCountingUserStore userStore) {
      try {
         return Optional.of(userStore.countLicenseRelevantUsers());
      } catch (Exception e) {
         LOG.warn(EDefaultLogType.GENERIC_LOG_TYPE, "Store Failure while counting license-relevant users", e);
         return Optional.empty();
      }
   }

   private List<LicenseUserCountingUserStore> getUserCounters() {
      return Optional.ofNullable(this.userStore)
         .stream()
         .flatMap(store -> Adapter.adapt(store).ifPossibleTo(LicenseUserCountingUserStore.class).stream())
         .collect(Collectors.toList());
   }

   private ActivationStateWatcher.ActivationResult deployLicense(String license) {
      return this.licenseDeployer.deploy(this.activeLicenseFile, license);
   }
}
