package com.airlock.iam.servicecontainer.app.application.configuration.radius;

import com.airlock.iam.core.misc.authen.result.AuthenticationResult;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.EnumProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.servicecontainer.app.application.service.radius.RadiusAttribute;
import com.airlock.iam.servicecontainer.app.application.service.radius.RadiusResponseModifier;
import com.airlock.iam.servicecontainer.app.application.service.radius.service.RadiusAttributeToReadableStringService;
import com.airlock.iam.servicecontainer.app.application.service.radius.wrapper.lib.IamRadiusServerAccessRequest;
import com.airlock.iam.servicecontainer.app.application.service.radius.wrapper.lib.IamRadiusServerResponse;
import com.google.common.annotations.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

@Plugin(
   description = "Adds an attribute to the RADIUS response packet. The value of the attribute is extracted from a configurable context data field of the authenticated user."
)
public final class AddAuthenteeAttributeConfig implements RadiusResponseModifier {
   private static final ELogger LOG = ELoggerFactory.getLogger(AddAuthenteeAttributeConfig.class);
   @EnumProperty(
      description = "The attribute to add to the RADIUS response. <p>The suffix of the attribute name gives a hint on the data type that this attribute expects in the context data field:<ul><li><strong>NV</strong> a named value meaning one of a known set of well string keys as defined in the latest FreeRadius dictionary.</li><li><strong>STRING</strong> an UTF8 encoded string.</li><li><strong>INT</strong> a number that can be represented by 4 bytes.</li><li><strong>BYTES</strong> either a byte array or a base64 encoded string.</li><li><strong>IPV4</strong> either an IPv4 address object, a host name or a raw 4 bytes internet address.</li><li><strong>IPV6</strong> either an IPv6 address object, a host name or a raw 16 bytes internet address.</li><li><strong>DATE</strong> either a Date object or a number representing seconds since 1.1.1970.</li></ul></p>",
      optional = false
   )
   private RadiusAttribute radiusAttribute;
   @StringProperty(
      description = "The context data field to get the value from. If the context data does not contain this field, the radius attribute will not be added.",
      optional = false
   )
   private String contextDataField;
   @BooleanProperty(
      description = "If true, only the attribute name (and not its value) will be logged. If false, the attribute value will be logged.",
      optional = true,
      defaultValue = "true"
   )
   private boolean isSensitive;

   @Override
   public void modifyResponse(IamRadiusServerResponse response, IamRadiusServerAccessRequest request, AuthenticationResult authResult) {
      if (authResult.authenticationSucceeded() && authResult.getAuthentee().getContextDataContainer().containsKey(this.contextDataField)) {
         Object objValue = authResult.getAuthentee().getContextDataContainer().get(this.contextDataField);
         this.radiusAttribute
            .createAttribute(objValue)
            .ifPresentOrElse(
               a -> {
                  LOG.info(
                     "modifyResponse() - adding attribute "
                        + this.radiusAttribute.name()
                        + ": "
                        + (this.isSensitive ? a.name() : RadiusAttributeToReadableStringService.toSensitiveString(a, this.radiusAttribute))
                  );
                  response.addAttribute(a);
               },
               () -> LOG.info("modifyResponse() - NOT adding attribute " + this.radiusAttribute.name())
            );
      } else {
         LOG.debug("modifyResponse() - NOT adding attribute " + this.radiusAttribute.name());
      }
   }

   @VisibleForTesting
   void setRadiusAttribute(RadiusAttribute radiusAttribute) {
      this.radiusAttribute = radiusAttribute;
   }

   @VisibleForTesting
   void setContextDataField(String contextDataField) {
      this.contextDataField = contextDataField;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static AddAuthenteeAttributeConfig.AddAuthenteeAttributeConfigBuilder builder() {
      return new AddAuthenteeAttributeConfig.AddAuthenteeAttributeConfigBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public AddAuthenteeAttributeConfig() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public AddAuthenteeAttributeConfig(RadiusAttribute radiusAttribute, String contextDataField, boolean isSensitive) {
      this.radiusAttribute = radiusAttribute;
      this.contextDataField = contextDataField;
      this.isSensitive = isSensitive;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class AddAuthenteeAttributeConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private RadiusAttribute radiusAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private String contextDataField;
      @SuppressFBWarnings(justification = "generated code")
      private boolean isSensitive;

      @SuppressFBWarnings(justification = "generated code")
      AddAuthenteeAttributeConfigBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public AddAuthenteeAttributeConfig.AddAuthenteeAttributeConfigBuilder radiusAttribute(RadiusAttribute radiusAttribute) {
         this.radiusAttribute = radiusAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AddAuthenteeAttributeConfig.AddAuthenteeAttributeConfigBuilder contextDataField(String contextDataField) {
         this.contextDataField = contextDataField;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AddAuthenteeAttributeConfig.AddAuthenteeAttributeConfigBuilder isSensitive(boolean isSensitive) {
         this.isSensitive = isSensitive;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AddAuthenteeAttributeConfig build() {
         return new AddAuthenteeAttributeConfig(this.radiusAttribute, this.contextDataField, this.isSensitive);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "AddAuthenteeAttributeConfig.AddAuthenteeAttributeConfigBuilder(radiusAttribute="
            + this.radiusAttribute
            + ", contextDataField="
            + this.contextDataField
            + ", isSensitive="
            + this.isSensitive
            + ")";
      }
   }
}
