// Copyright 2026 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.package google.registry.flows;

package google.registry.flows;

import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_11;
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_12;
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_0_6;
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.FEE_1_00;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import google.registry.model.eppcommon.EppXmlTransformer;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * Normalizes Fee extension namespace tags in EPP XML response messages.
 *
 * <p>Nomulus currently supports multiple versions of the Fee extension. With the current XML
 * tooling, the namespace of every version is included in each EPP response, and as a result must
 * use a unique XML tag. E.g., fee for extension v0.6, and fee12 for extension v0.12.
 *
 * <p>Some registrars are not XML namespace-aware and rely on the XML tags being specific literals.
 * This makes it difficult to perform seamless rollout of new versions: if Nomulus reassigns a tag
 * literal to a different version, it effectively forces all these registrars to upgrade at the time
 * of the deployment.
 *
 * <p>This class can be used to normalize the namespace tag in EPP responses. Since every response
 * message may use at most one version of the Fee extension, we can remove declared but unused
 * versions from the message, thus freeing up the canonical tag ('fee') for the active version.
 */
public class FeeExtensionXmlTagNormalizer {

  // So far we only have Fee extensions to process
  private static final String CANONICAL_FEE_TAG = "fee";

  private static final ImmutableSet<ServiceExtension> FEE_EXTENSIONS =
      ImmutableSet.of(FEE_0_6, FEE_0_11, FEE_0_12, FEE_1_00);

  private static final Pattern FEE_EXTENSION_IN_USE_PATTERN =
      Pattern.compile(feeExtensionInUseRegex());

  @VisibleForTesting
  static String feeExtensionInUseRegex() {
    return FEE_EXTENSIONS.stream()
        .map(ServiceExtension::getXmlTag)
        .map(tag -> String.format("\\b(%s):", tag))
        .collect(Collectors.joining("|"));
  }

  /**
   * Returns a EPP response that uses the canonical tag ({@code fee}) for the fee extension.
   *
   * <p>This method replaces any versioned tag, e.g., {@code fee12} with the canonical tag. It also
   * removes unused namespace declarations and update the tag in the remaining declaration.
   *
   * <p>The input {@code xml} must be an EPP response message generated by the {@link
   * EppXmlTransformer}. With this assumption, we can use regular expressions which is 10X faster
   * than XML stream parsers.
   */
  public static String normalize(String xml) {
    Optional<String> maybeFeeTagInUse = findFeeExtensionInUse(xml);
    if (maybeFeeTagInUse.isEmpty()) {
      return xml;
    }
    String feeTagInUse = maybeFeeTagInUse.get();
    String normalized = xml;
    for (ServiceExtension serviceExtension : FEE_EXTENSIONS) {
      if (serviceExtension.getXmlTag().equals(feeTagInUse)) {
        normalized = normalizeExtensionInUse(feeTagInUse, serviceExtension.getUri(), normalized);
      } else {
        normalized =
            removeUnusedExtension(
                serviceExtension.getXmlTag(), serviceExtension.getUri(), normalized);
      }
    }
    return normalized;
  }

  static String removeUnusedExtension(String tag, String uri, String xml) {
    String declaration = String.format("xmlns:%s=\"%s\"", tag, uri);
    // There must be a leading whitespace, and it can be safely removed with the declaration.
    return xml.replaceAll(String.format("\\s%s", declaration), "");
  }

  static String normalizeExtensionInUse(String tagInUse, String uriInUse, String xml) {
    if (tagInUse.equals(CANONICAL_FEE_TAG)) {
      return xml;
    }
    // Change the tag in the namespace declaration:
    String currentDeclaration = String.format("xmlns:%s=\"%s\"", tagInUse, uriInUse);
    String desiredDeclaraion = String.format("xmlns:fee=\"%s\"", uriInUse);
    // The new tag at each site of use, with trailing colon:
    String newTagWithColon = CANONICAL_FEE_TAG + ":";
    return xml.replaceAll(String.format("\\b%s:", tagInUse), newTagWithColon)
        .replaceAll(currentDeclaration, desiredDeclaraion);
  }

  static Optional<String> findFeeExtensionInUse(String xml) {
    Matcher matcher = FEE_EXTENSION_IN_USE_PATTERN.matcher(xml);

    if (!matcher.find()) {
      return Optional.empty();
    }
    // We know only one extension is in use, so we can return on the first match
    for (int i = 1; i <= matcher.groupCount(); i++) {
      if (matcher.group(i) != null) {
        return Optional.of(matcher.group(i));
      }
    }
    throw new IllegalStateException("Should not reach here. Bad FEE_EXTENSION_IN_USE_PATTERN?");
  }
}
