content: package pl.allegro.tech.hermes.api;
  
  import static pl.allegro.tech.hermes.api.constraints.Names.ALLOWED_NAME_REGEX;
  
  import com.fasterxml.jackson.annotation.JsonCreator;
  import com.fasterxml.jackson.annotation.JsonIgnore;
  import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  import com.fasterxml.jackson.annotation.JsonProperty;
  import jakarta.validation.Valid;
  import jakarta.validation.constraints.NotEmpty;
  import jakarta.validation.constraints.NotNull;
  import jakarta.validation.constraints.Pattern;
  import java.time.Instant;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import pl.allegro.tech.hermes.api.constraints.ValidContentType;
  import pl.allegro.tech.hermes.api.subscription.metrics.SubscriptionMetricsConfig;
  
  @ValidContentType(message = "AVRO content type is not supported in BATCH delivery mode")
  @JsonIgnoreProperties(
      value = {"createdAt", "modifiedAt"},
      allowGetters = true)
  public class Subscription implements Anonymizable {
  
    @NotNull private final MonitoringDetails monitoringDetails;
    private final SubscriptionName subscriptionName;
    @Valid @NotNull private TopicName topicName;
  
    @NotEmpty
    @Pattern(regexp = ALLOWED_NAME_REGEX)
    private String name;
  
    private State state = State.PENDING;
    @NotNull @Valid private EndpointAddress endpoint;
    @NotNull private ContentType contentType = ContentType.JSON;
    @NotNull private String description;
    @Valid private SubscriptionPolicy serialSubscriptionPolicy;
    @Valid private BatchSubscriptionPolicy batchSubscriptionPolicy;
  
    /** Use trackingMode field instead. */
    @Deprecated private boolean trackingEnabled = false;
  
    private TrackingMode trackingMode = TrackingMode.TRACKING_OFF;
    private boolean http2Enabled = false;
    private boolean profilingEnabled = false;
    private long profilingThresholdMs = 0;
    @Valid @NotNull private OwnerId owner;
    @NotNull private DeliveryType deliveryType = DeliveryType.SERIAL;
    @NotNull private SubscriptionMode mode = SubscriptionMode.ANYCAST;
    private List<MessageFilterSpecification> filters = new ArrayList<>();
  
    private List<Header> headers;
  
    private EndpointAddressResolverMetadata endpointAddressResolverMetadata;
  
    @Valid private SubscriptionOAuthPolicy oAuthPolicy;
  
    private boolean subscriptionIdentityHeadersEnabled;
  
    private boolean autoDeleteWithTopicEnabled;
  
    private Instant createdAt;
  
    private Instant modifiedAt;
  
    @Valid @NotNull private SubscriptionMetricsConfig metricsConfig;
  
    private Subscription(
        TopicName topicName,
        String name,
        EndpointAddress endpoint,
        State state,
        String description,
        Object subscriptionPolicy,
        boolean trackingEnabled,
        TrackingMode trackingMode,
        OwnerId owner,
        MonitoringDetails monitoringDetails,
        ContentType contentType,
        DeliveryType deliveryType,
        List<MessageFilterSpecification> filters,
        SubscriptionMode mode,
        List<Header> headers,
        EndpointAddressResolverMetadata endpointAddressResolverMetadata,
        SubscriptionOAuthPolicy oAuthPolicy,
        boolean http2Enabled,
        boolean profilingEnabled,
        long profilingThresholdMs,
        boolean subscriptionIdentityHeadersEnabled,
        boolean autoDeleteWithTopicEnabled,
        SubscriptionMetricsConfig metricsConfig) {
      this.topicName = topicName;
      this.name = name;
      this.endpoint = endpoint;
      this.state = state != null ? state : State.PENDING;
      this.description = description;
      this.trackingEnabled = trackingEnabled;
      this.trackingMode = trackingMode;
      this.owner = owner;
      this.monitoringDetails =
          monitoringDetails == null ? MonitoringDetails.EMPTY : monitoringDetails;
      this.contentType = contentType == null ? ContentType.JSON : contentType;
      this.deliveryType = deliveryType;
      this.batchSubscriptionPolicy =
          this.deliveryType == DeliveryType.BATCH
              ? (BatchSubscriptionPolicy) subscriptionPolicy
              : null;
      this.serialSubscriptionPolicy =
          this.deliveryType == DeliveryType.SERIAL ? (SubscriptionPolicy) subscriptionPolicy : null;
      this.filters = filters;
      this.mode = mode;
      this.http2Enabled = http2Enabled;
      this.profilingEnabled = profilingEnabled;
      this.profilingThresholdMs = profilingThresholdMs;
      this.subscriptionName = new SubscriptionName(name, topicName);
      this.headers = headers;
      this.endpointAddressResolverMetadata = endpointAddressResolverMetadata;
      this.oAuthPolicy = oAuthPolicy;
      this.subscriptionIdentityHeadersEnabled = subscriptionIdentityHeadersEnabled;
      this.autoDeleteWithTopicEnabled = autoDeleteWithTopicEnabled;
      this.metricsConfig = metricsConfig == null ? SubscriptionMetricsConfig.DISABLED : metricsConfig;
    }
  
    public static Subscription createSerialSubscription(
        TopicName topicName,
        String name,
        EndpointAddress endpoint,
        State state,
        String description,
        SubscriptionPolicy subscriptionPolicy,
        boolean trackingEnabled,
        TrackingMode trackingMode,
        OwnerId owner,
        MonitoringDetails monitoringDetails,
        ContentType contentType,
        List<MessageFilterSpecification> filters,
        SubscriptionMode mode,
        List<Header> headers,
        EndpointAddressResolverMetadata endpointAddressResolverMetadata,
        SubscriptionOAuthPolicy oAuthPolicy,
        boolean http2Enabled,
        boolean profilingEnabled,
        long profilingThresholdMs,
        boolean subscriptionIdentityHeadersEnabled,
        boolean autoDeleteWithTopicEnabled,
        SubscriptionMetricsConfig metricsConfig) {
      return new Subscription(
          topicName,
          name,
          endpoint,
          state,
          description,
          subscriptionPolicy,
          trackingEnabled,
          trackingMode,
          owner,
          monitoringDetails,
          contentType,
          DeliveryType.SERIAL,
          filters,
          mode,
          headers,
          endpointAddressResolverMetadata,
          oAuthPolicy,
          http2Enabled,
          profilingEnabled,
          profilingThresholdMs,
          subscriptionIdentityHeadersEnabled,
          autoDeleteWithTopicEnabled,
          metricsConfig);
    }
  
    public static Subscription createBatchSubscription(
        TopicName topicName,
        String name,
        EndpointAddress endpoint,
        State state,
        String description,
        BatchSubscriptionPolicy subscriptionPolicy,
        boolean trackingEnabled,
        TrackingMode trackingMode,
        OwnerId owner,
        MonitoringDetails monitoringDetails,
        ContentType contentType,
        List<MessageFilterSpecification> filters,
        List<Header> headers,
        EndpointAddressResolverMetadata endpointAddressResolverMetadata,
        SubscriptionOAuthPolicy oAuthPolicy,
        boolean http2Enabled,
        boolean subscriptionIdentityHeadersEnabled,
        boolean autoDeleteWithTopicEnabled) {
      return new Subscription(
          topicName,
          name,
          endpoint,
          state,
          description,
          subscriptionPolicy,
          trackingEnabled,
          trackingMode,
          owner,
          monitoringDetails,
          contentType,
          DeliveryType.BATCH,
          filters,
          SubscriptionMode.ANYCAST,
          headers,
          endpointAddressResolverMetadata,
          oAuthPolicy,
          http2Enabled,
          false,
          0,
          subscriptionIdentityHeadersEnabled,
          autoDeleteWithTopicEnabled,
          SubscriptionMetricsConfig.DISABLED);
    }
  
    @JsonCreator
    public static Subscription create(
        @JsonProperty("topicName") String topicName,
        @JsonProperty("name") String name,
        @JsonProperty("endpoint") EndpointAddress endpoint,
        @JsonProperty("state") State state,
        @JsonProperty("description") String description,
        @JsonProperty("subscriptionPolicy") Map<String, Object> subscriptionPolicy,
        @JsonProperty("trackingEnabled") boolean trackingEnabled,
        @JsonProperty("trackingMode") String trackingMode,
        @JsonProperty("owner") OwnerId owner,
        @JsonProperty("monitoringDetails") MonitoringDetails monitoringDetails,
        @JsonProperty("contentType") ContentType contentType,
        @JsonProperty("deliveryType") DeliveryType deliveryType,
        @JsonProperty("filters") List<MessageFilterSpecification> filters,
        @JsonProperty("mode") SubscriptionMode mode,
        @JsonProperty("headers") List<Header> headers,
        @JsonProperty("endpointAddressResolverMetadata")
            EndpointAddressResolverMetadata endpointAddressResolverMetadata,
        @JsonProperty("oAuthPolicy") SubscriptionOAuthPolicy oAuthPolicy,
        @JsonProperty("http2Enabled") boolean http2Enabled,
        @JsonProperty("profilingEnabled") boolean profilingEnabled,
        @JsonProperty("profilingThresholdMs") long profilingThresholdMs,
        @JsonProperty("subscriptionIdentityHeadersEnabled")
            boolean subscriptionIdentityHeadersEnabled,
        @JsonProperty("autoDeleteWithTopicEnabled") boolean autoDeleteWithTopicEnabled,
        @JsonProperty("metricsConfig") SubscriptionMetricsConfig metricsConfig) {
  
      DeliveryType validDeliveryType = deliveryType == null ? DeliveryType.SERIAL : deliveryType;
      SubscriptionMode subscriptionMode = mode == null ? SubscriptionMode.ANYCAST : mode;
      Map<String, Object> validSubscriptionPolicy =
          subscriptionPolicy == null ? new HashMap<>() : subscriptionPolicy;
  
      TrackingMode validTrackingMode =
          TrackingMode.fromString(trackingMode)
              .orElse(trackingEnabled ? TrackingMode.TRACK_ALL : TrackingMode.TRACKING_OFF);
      boolean validTrackingEnabled = validTrackingMode != TrackingMode.TRACKING_OFF;
  
      return new Subscription(
          TopicName.fromQualifiedName(topicName),
          name,
          endpoint,
          state,
          description,
          validDeliveryType == DeliveryType.SERIAL
              ? SubscriptionPolicy.create(validSubscriptionPolicy)
              : BatchSubscriptionPolicy.create(validSubscriptionPolicy),
          validTrackingEnabled,
          validTrackingMode,
          owner,
          monitoringDetails,
          contentType,
          validDeliveryType,
          filters == null ? Collections.emptyList() : filters,
          subscriptionMode,
          headers == null ? Collections.emptyList() : headers,
          endpointAddressResolverMetadata == null
              ? EndpointAddressResolverMetadata.empty()
              : endpointAddressResolverMetadata,
          oAuthPolicy,
          http2Enabled,
          profilingEnabled,
          profilingThresholdMs,
          subscriptionIdentityHeadersEnabled,
          autoDeleteWithTopicEnabled,
          metricsConfig);
    }
  
    @Override
    public int hashCode() {
      return Objects.hash(
          endpoint,
          topicName,
          name,
          description,
          serialSubscriptionPolicy,
          batchSubscriptionPolicy,
          trackingEnabled,
          trackingMode,
          owner,
          monitoringDetails,
          contentType,
          filters,
          mode,
          headers,
          endpointAddressResolverMetadata,
          oAuthPolicy,
          http2Enabled,
          subscriptionIdentityHeadersEnabled,
          metricsConfig);
    }
  
    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null || getClass() != obj.getClass()) {
        return false;
      }
      final Subscription other = (Subscription) obj;
  
      return Objects.equals(this.endpoint, other.endpoint)
          && Objects.equals(this.topicName, other.topicName)
          && Objects.equals(this.name, other.name)
          && Objects.equals(this.description, other.description)
          && Objects.equals(this.serialSubscriptionPolicy, other.serialSubscriptionPolicy)
          && Objects.equals(this.batchSubscriptionPolicy, other.batchSubscriptionPolicy)
          && Objects.equals(this.trackingEnabled, other.trackingEnabled)
          && Objects.equals(this.trackingMode, other.trackingMode)
          && Objects.equals(this.owner, other.owner)
          && Objects.equals(this.monitoringDetails, other.monitoringDetails)
          && Objects.equals(this.contentType, other.contentType)
          && Objects.equals(this.filters, other.filters)
          && Objects.equals(this.mode, other.mode)
          && Objects.equals(this.headers, other.headers)
          && Objects.equals(
              this.endpointAddressResolverMetadata, other.endpointAddressResolverMetadata)
          && Objects.equals(this.http2Enabled, other.http2Enabled)
          && Objects.equals(this.profilingEnabled, other.profilingEnabled)
          && Objects.equals(this.profilingThresholdMs, other.profilingThresholdMs)
          && Objects.equals(this.oAuthPolicy, other.oAuthPolicy)
          && Objects.equals(
              this.subscriptionIdentityHeadersEnabled, other.subscriptionIdentityHeadersEnabled)
          && Objects.equals(this.autoDeleteWithTopicEnabled, other.autoDeleteWithTopicEnabled)
          && Objects.equals(this.metricsConfig, other.metricsConfig);
    }
  
    @JsonIgnore
    public SubscriptionName getQualifiedName() {
      return subscriptionName;
    }
  
    public EndpointAddress getEndpoint() {
      return endpoint;
    }
  
    @JsonProperty("topicName")
    public String getQualifiedTopicName() {
      return TopicName.toQualifiedName(topicName);
    }
  
    @JsonIgnore
    public TopicName getTopicName() {
      return topicName;
    }
  
    public String getName() {
      return name;
    }
  
    public String getDescription() {
      return description;
    }
  
    public State getState() {
      return state;
    }
  
    public void setState(State state) {
      this.state = state;
    }
  
    @JsonProperty("subscriptionPolicy")
    public Object getSubscriptionPolicy() {
      return isBatchSubscription() ? batchSubscriptionPolicy : serialSubscriptionPolicy;
    }
  
    public boolean isTrackingEnabled() {
      return trackingMode != TrackingMode.TRACKING_OFF;
    }
  
    @JsonProperty("trackingMode")
    public String getTrackingModeString() {
      return trackingMode.getValue();
    }
  
    @JsonIgnore
    public TrackingMode getTrackingMode() {
      return trackingMode;
    }
  
    public OwnerId getOwner() {
      return owner;
    }
  
    public MonitoringDetails getMonitoringDetails() {
      return monitoringDetails;
    }
  
    public ContentType getContentType() {
      return contentType;
    }
  
    public DeliveryType getDeliveryType() {
      return deliveryType;
    }
  
    public List<MessageFilterSpecification> getFilters() {
      return Collections.unmodifiableList(filters);
    }
  
    public List<Header> getHeaders() {
      return Collections.unmodifiableList(headers);
    }
  
    public EndpointAddressResolverMetadata getEndpointAddressResolverMetadata() {
      return endpointAddressResolverMetadata;
    }
  
    @JsonIgnore
    public boolean isBatchSubscription() {
      return this.deliveryType == DeliveryType.BATCH;
    }
  
    @JsonIgnore
    public BatchSubscriptionPolicy getBatchSubscriptionPolicy() {
      return batchSubscriptionPolicy;
    }
  
    @JsonIgnore
    public SubscriptionPolicy getSerialSubscriptionPolicy() {
      return serialSubscriptionPolicy;
    }
  
    public void setSerialSubscriptionPolicy(SubscriptionPolicy serialSubscriptionPolicy) {
      this.serialSubscriptionPolicy = serialSubscriptionPolicy;
    }
  
    @JsonIgnore
    public boolean isActive() {
      return state == State.ACTIVE || state == State.PENDING;
    }
  
    public SubscriptionMode getMode() {
      return mode;
    }
  
    @JsonProperty("oAuthPolicy")
    public SubscriptionOAuthPolicy getOAuthPolicy() {
      return oAuthPolicy;
    }
  
    @JsonIgnore
    public boolean hasOAuthPolicy() {
      return oAuthPolicy != null;
    }
  
    @JsonIgnore
    public boolean isSeverityNotImportant() {
      return getMonitoringDetails().getSeverity() == MonitoringDetails.Severity.NON_IMPORTANT;
    }
  
    public boolean isHttp2Enabled() {
      return http2Enabled;
    }
  
    public boolean isProfilingEnabled() {
      return profilingEnabled;
    }
  
    public long getProfilingThresholdMs() {
      return profilingThresholdMs;
    }
  
    public boolean isSubscriptionIdentityHeadersEnabled() {
      return subscriptionIdentityHeadersEnabled;
    }
  
    public Instant getCreatedAt() {
      return createdAt;
    }
  
    public void setCreatedAt(Long createdAt) {
      this.createdAt = Instant.ofEpochMilli(createdAt);
    }
  
    public Instant getModifiedAt() {
      return modifiedAt;
    }
  
    public void setModifiedAt(Long modifiedAt) {
      this.modifiedAt = Instant.ofEpochMilli(modifiedAt);
    }
  
    public boolean isAutoDeleteWithTopicEnabled() {
      return autoDeleteWithTopicEnabled;
    }
  
    public SubscriptionMetricsConfig getMetricsConfig() {
      return metricsConfig;
    }
  
    @Override
    public Subscription anonymize() {
      if (getEndpoint() != null && getEndpoint().containsCredentials() || hasOAuthPolicy()) {
        return new Subscription(
            topicName,
            name,
            endpoint.anonymize(),
            state,
            description,
            deliveryType == DeliveryType.BATCH ? batchSubscriptionPolicy : serialSubscriptionPolicy,
            trackingEnabled,
            trackingMode,
            owner,
            monitoringDetails,
            contentType,
            deliveryType,
            filters,
            mode,
            headers,
            endpointAddressResolverMetadata,
            oAuthPolicy != null ? oAuthPolicy.anonymize() : null,
            http2Enabled,
            profilingEnabled,
            profilingThresholdMs,
            subscriptionIdentityHeadersEnabled,
            autoDeleteWithTopicEnabled,
            metricsConfig);
      }
      return this;
    }
  
    @Override
    public String toString() {
      return "Subscription(" + getQualifiedName() + ")";
    }
  
    public enum State {
      PENDING,
      ACTIVE,
      SUSPENDED
    }
  }
  