package com.airlock.iam.oauth2.application.configuration.claims;

import com.airlock.iam.core.application.configuration.contextdata.DateAndTimeContextDataItemNameConfig;
import com.airlock.iam.oauth2.api.application.configuration.claims.ClaimConditionConfig;
import com.airlock.iam.oauth2.api.domain.model.claims.UserClaimExtractor;
import com.airlock.iam.oauth2.domain.model.claims.ContextDataClaimValueExtractor;
import com.airlock.iam.oauth2.domain.model.claims.custom.CustomContextDataDateAsStringClaimExtractor;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.framework.application.configuration.validator.FastDateFormatPatternValidator;
import com.airlock.iam.plugin.framework.application.configuration.validator.ZoneIdValidator;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.time.ZoneId;
import java.util.TimeZone;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;

@Plugin(
   description = "A custom claim containing a <i>Date and Time</i> value (from the context data of the user) formatted as string.</p>",
   displayName = "Formatted Date And Time Context Data Custom Claim"
)
@PluginLicenseRestriction(moduleAffiliation = "OAuthServer")
public final class CustomContextDataDateAsStringClaimConfig extends GenericCustomClaimConfig {
   @PluginProperty(
      description = "The name of the date and time context data element to be returned in this claim.",
      optional = false,
      displayName = "Context Data"
   )
   private DateAndTimeContextDataItemNameConfig contextDataName;
   @StringProperty(
      description = "The pattern to format the date value with.",
      optional = true,
      defaultValue = "yyyy-MM-dd",
      suggestedValues = {"yyyy-MM-dd", "dd.MM.yyyy", "MM/dd/yyyy", "dd-MMM-yyyy HH:mm:ss", "yyyy-MM-dd HH:mm:ss"}
   )
   private String pattern;
   private static final String PATTERN_FIELD = "pattern";
   @StringProperty(
      description = "<p>The timezone that should be used when formatting the date value. If nothing is configured the timezone of the server is taken.</p>\n<p>See <a href=\"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/ZoneId.html\"> for more examples.</p>\n",
      optional = true,
      suggestedValues = {"Europe/Zurich", "US/Pacific", "CET", "GMT", "UTC"}
   )
   private String timezone;
   private static final String TIMEZONE_FIELD = "timezone";
   private FastDateFormat dateFormat;

   public DateAndTimeContextDataItemNameConfig getContextDataName() {
      return this.contextDataName;
   }

   public FastDateFormat getDateFormat() {
      return this.dateFormat;
   }

   public CustomContextDataDateAsStringClaimConfig(
      String claimName, ClaimConditionConfig claimCondition, DateAndTimeContextDataItemNameConfig contextDataName, String pattern, String timezone
   ) {
      super(claimName, claimCondition);
      this.contextDataName = contextDataName;
      this.pattern = pattern;
      this.timezone = timezone;
   }

   @ConfigurationValidator
   public static void validateConfiguredTimezoneAndDatePattern(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      FastDateFormatPatternValidator.validateFastDateFormatPattern(config, "pattern", env, context);
      ZoneIdValidator.validateZoneId(config, "timezone", env, context);
   }

   @AfterConfigActivation
   void prepareDateFormat() {
      this.dateFormat = this.createDateFormat();
   }

   private FastDateFormat createDateFormat() {
      if (StringUtils.isBlank(this.timezone)) {
         return FastDateFormat.getInstance(this.pattern, TimeZone.getDefault());
      }

      ZoneId zoneId = ZoneId.of(this.timezone);
      return FastDateFormat.getInstance(this.pattern, TimeZone.getTimeZone(zoneId));
   }

   @Override
   public UserClaimExtractor<?> logic() {
      return new CustomContextDataDateAsStringClaimExtractor(
         this::getClaimName,
         this::getClaimCondition,
         () -> new ContextDataClaimValueExtractor(this.contextDataName.getContextDataName(), this.getClaimName()),
         () -> this.dateFormat::format
      );
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class CustomContextDataDateAsStringClaimConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String claimName;
      @SuppressFBWarnings(justification = "generated code")
      private ClaimConditionConfig claimCondition;
      @SuppressFBWarnings(justification = "generated code")
      private DateAndTimeContextDataItemNameConfig contextDataName;
      @SuppressFBWarnings(justification = "generated code")
      private String pattern;
      @SuppressFBWarnings(justification = "generated code")
      private String timezone;

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

      @SuppressFBWarnings(justification = "generated code")
      public CustomContextDataDateAsStringClaimConfig.CustomContextDataDateAsStringClaimConfigBuilder claimName(String claimName) {
         this.claimName = claimName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CustomContextDataDateAsStringClaimConfig.CustomContextDataDateAsStringClaimConfigBuilder claimCondition(ClaimConditionConfig claimCondition) {
         this.claimCondition = claimCondition;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CustomContextDataDateAsStringClaimConfig.CustomContextDataDateAsStringClaimConfigBuilder contextDataName(
         DateAndTimeContextDataItemNameConfig contextDataName
      ) {
         this.contextDataName = contextDataName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CustomContextDataDateAsStringClaimConfig.CustomContextDataDateAsStringClaimConfigBuilder pattern(String pattern) {
         this.pattern = pattern;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CustomContextDataDateAsStringClaimConfig.CustomContextDataDateAsStringClaimConfigBuilder timezone(String timezone) {
         this.timezone = timezone;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public CustomContextDataDateAsStringClaimConfig build() {
         return new CustomContextDataDateAsStringClaimConfig(this.claimName, this.claimCondition, this.contextDataName, this.pattern, this.timezone);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "CustomContextDataDateAsStringClaimConfig.CustomContextDataDateAsStringClaimConfigBuilder(claimName="
            + this.claimName
            + ", claimCondition="
            + this.claimCondition
            + ", contextDataName="
            + this.contextDataName
            + ", pattern="
            + this.pattern
            + ", timezone="
            + this.timezone
            + ")";
      }
   }
}
