package com.airlock.iam.system.version;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nonnull;
import java.util.Comparator;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressFBWarnings(value = "REDOS", justification = "Match of VERSION_PATTERN is never against untrusted input and ReDOS is only theoretical.")
public final class IamVersion implements Comparable<IamVersion> {
   private static final Pattern VERSION_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)(?:\\.(?<patch>\\d+))?+(?<labels>-[a-zA-Z0-9.-]+)?");
   private final String versionString;
   private final Integer major;
   private final Integer minor;
   private final Integer patch;

   public static IamVersion fromString(String version) {
      if (!isValidVersion(version)) {
         throw new IllegalArgumentException("Unknown version format: " + version);
      } else {
         String versionString = version.trim();
         Matcher match = VERSION_PATTERN.matcher(versionString);
         if (!match.matches()) {
            throw new IllegalStateException();
         } else {
            return new IamVersion(
               versionString,
               parseOptionalInteger(match.group("major")).orElseThrow(IllegalStateException::new),
               parseOptionalInteger(match.group("minor")).orElseThrow(IllegalStateException::new),
               parseOptionalInteger(match.group("patch")).orElse(0)
            );
         }
      }
   }

   public static boolean isValidVersion(String version) {
      return version != null && VERSION_PATTERN.matcher(version.trim()).matches();
   }

   public boolean matchesMajorMinorVersionOf(IamVersion version) {
      return this.major.equals(version.major) && this.minor.equals(version.minor);
   }

   @Override
   public String toString() {
      return this.toTechnicalVersionString();
   }

   public String toTechnicalVersionString() {
      StringBuilder builder = new StringBuilder();
      builder.append(this.major);
      builder.append('.');
      builder.append(this.minor);
      if (this.patch > 0) {
         builder.append('.');
         builder.append(this.patch);
      }

      return builder.toString();
   }

   public String toFullVersionString() {
      return this.versionString;
   }

   public String toMajorMinor() {
      return this.major + "." + this.minor;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj) {
         return true;
      }

      if (obj == null) {
         return false;
      }

      if (!this.getClass().equals(obj.getClass())) {
         return false;
      }

      IamVersion version = (IamVersion)obj;
      return this.toTechnicalVersionString().equals(version.toTechnicalVersionString());
   }

   public int compareTo(@Nonnull IamVersion other) {
      return Comparator.<IamVersion>comparingInt(v -> v.major).thenComparingInt(v -> v.minor).thenComparingInt(v -> v.patch).compare(this, other);
   }

   @Override
   public int hashCode() {
      return this.toTechnicalVersionString().hashCode();
   }

   private static Optional<Integer> parseOptionalInteger(String numberString) throws NumberFormatException {
      return numberString != null && !numberString.isEmpty() ? Optional.of(Integer.parseInt(numberString)) : Optional.empty();
   }

   @SuppressFBWarnings(justification = "generated code")
   public IamVersion(String versionString, Integer major, Integer minor, Integer patch) {
      this.versionString = versionString;
      this.major = major;
      this.minor = minor;
      this.patch = patch;
   }
}
