package com.sun.identity.saml2.common;

import com.airlock.iam.opensso.jaxb.entityconfig.BaseConfigType;
import com.sun.identity.saml.common.SAMLUtilsCommon;
import com.sun.identity.saml2.meta.SAML2MetaUtils;
import com.sun.identity.shared.configuration.SystemPropertiesManager;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.shared.locale.Locale;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

public class SAML2SDKUtils {
   public static final Debug DEBUG = Debug.getInstance("libSAML2");
   public static final String BUNDLE_NAME = "libSAML2";
   public static final ResourceBundle BUNDLE = Locale.getInstallResourceBundle("libSAML2");
   protected static final String SAML2ID_PREFIX = "s2";
   private static final SecureRandom RANDOM = new SecureRandom();
   private static final Map<String, Class<?>> CLASS_MAPPING = new HashMap<>();
   public static final String ADVICE = "Advice";
   public static final String ASSERTION = "Assertion";
   public static final String ASSERTION_ID_REF = "AssertionIDRef";
   public static final String ATTRIBUTE = "Attribute";
   public static final String ATTRIBUTE_STATEMENT = "AttributeStatement";
   public static final String AUDIENCE_RESTRICTION = "AudienceRestriction";
   public static final String AUTHN_CONTEXT = "AuthnContext";
   public static final String AUTHN_STATEMENT = "AuthnStatement";
   public static final String AUTHZ_DECISION_STATEMENT = "AuthzDecisionStatement";
   public static final String BASEID = "BaseID";
   public static final String CONDITION = "Condition";
   public static final String CONDITIONS = "Conditions";
   public static final String ENCRYPTED_ASSERTION = "EncryptedAssertion";
   public static final String ENCRYPTED_ATTRIBUTE = "EncryptedAttribute";
   public static final String ENCRYPTEDID = "EncryptedID";
   public static final String ISSUER = "Issuer";
   public static final String NAMEID = "NameID";
   public static final String ONE_TIME_USE = "OneTimeUse";
   public static final String PROXY_RESTRICTION = "ProxyRestriction";
   public static final String SUBJECT_CONFIRMATION_DATA = "SubjectConfirmationData";
   public static final String SUBJECT_CONFIRMATION = "SubjectConfirmation";
   public static final String SUBJECT = "Subject";
   public static final String SUBJECT_LOCALITY = "SubjectLocality";
   public static final String ARTIFACT = "Artifact";
   public static final String ARTIFACT_RESOLVE = "ArtifactResolve";
   public static final String ARTIFACT_RESPONSE = "ArtifactResponse";
   public static final String AUTHN_REQUEST = "AuthnRequest";
   public static final String ECP_RESPONSE = "ECPResponse";
   public static final String EXTENSIONS = "Extensions";
   public static final String GET_COMPLETE = "GetComplete";
   public static final String IDPENTRY = "IDPEntry";
   public static final String IDPLIST = "IDPList";
   public static final String LOGOUT_REQUEST = "LogoutRequest";
   public static final String LOGOUT_RESPONSE = "LogoutResponse";
   public static final String NAMEID_POLICY = "NameIDPolicy";
   public static final String NEW_ENCRYPTEDID = "NewEncryptedID";
   public static final String NEWID = "NewID";
   public static final String REQUESTED_AUTHN_CONTEXT = "RequestedAuthnContext";
   public static final String REQUESTERID = "RequesterID";
   public static final String RESPONSE = "Response";
   public static final String SCOPING = "Scoping";
   public static final String SESSION_INDEX = "SessionIndex";
   public static final String STATUS_CODE = "StatusCode";
   public static final String STATUS_DETAIL = "StatusDetail";
   public static final String STATUS = "Status";
   public static final String STATUS_MESSAGE = "StatusMessage";
   private static final String[] INTERFACE_NAMES = new String[]{
      "Advice",
      "Assertion",
      "AssertionIDRef",
      "Attribute",
      "AttributeStatement",
      "AudienceRestriction",
      "AuthnContext",
      "AuthnStatement",
      "AuthzDecisionStatement",
      "BaseID",
      "Condition",
      "Conditions",
      "EncryptedAssertion",
      "EncryptedAttribute",
      "EncryptedID",
      "Issuer",
      "NameID",
      "OneTimeUse",
      "ProxyRestriction",
      "SubjectConfirmationData",
      "SubjectConfirmation",
      "Subject",
      "SubjectLocality",
      "Artifact",
      "ArtifactResolve",
      "ArtifactResponse",
      "AuthnRequest",
      "Extensions",
      "GetComplete",
      "IDPEntry",
      "IDPList",
      "LogoutRequest",
      "LogoutResponse",
      "NameIDPolicy",
      "NewEncryptedID",
      "NewID",
      "RequestedAuthnContext",
      "RequesterID",
      "Response",
      "Scoping",
      "SessionIndex",
      "StatusCode",
      "StatusDetail",
      "Status",
      "StatusMessage"
   };
   private static final Class<?>[] ART_PARAM = new Class[]{byte[].class, int.class, String.class, String.class};
   private static final Class<?>[] STRING_PARAM = new Class[]{String.class};
   private static final Class<?>[] ELEMENT_PARAM = new Class[]{Element.class};

   protected SAML2SDKUtils() {
   }

   public static Object getObjectInstance(String iName) {
      Class<?> implClass = CLASS_MAPPING.get(iName);
      if (implClass != null) {
         try {
            return implClass.getDeclaredConstructor().newInstance();
         } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ie) {
            DEBUG.error("SAML2SDKUtils.getDefaultInstance: " + iName, ie);
         }
      }

      return null;
   }

   public static Object getObjectInstance(String iName, String value) {
      Class<?> implClass = CLASS_MAPPING.get(iName);
      if (implClass == null) {
         return null;
      }

      if (DEBUG.messageEnabled()) {
         DEBUG.message("SAML2SDKUtils.getObjectInstance: new customized impl (String) instance for " + iName);
      }

      Object[] params = new Object[]{value};
      return getObjectInstance(implClass, STRING_PARAM, params);
   }

   public static Object getObjectInstance(String iName, Element value) {
      Class<?> implClass = CLASS_MAPPING.get(iName);
      if (implClass == null) {
         return null;
      }

      if (DEBUG.messageEnabled()) {
         DEBUG.message("SAML2SDKUtils.getObjectInstance: new customized impl instance (Element) for " + iName);
      }

      Object[] params = new Object[]{value};
      return getObjectInstance(implClass, ELEMENT_PARAM, params);
   }

   public static Object getObjectInstance(String iName, byte[] typecode, int endpointIndex, String sourceID, String messageHandle) {
      Class<?> implClass = CLASS_MAPPING.get(iName);
      if (implClass == null) {
         return null;
      }

      if (DEBUG.messageEnabled()) {
         DEBUG.message("SAML2SDKUtils.getObjectInstance: new customized impl (4) instance for " + iName);
      }

      Object[] params = new Object[]{typecode, endpointIndex, sourceID, messageHandle};
      return getObjectInstance(implClass, ART_PARAM, params);
   }

   private static Object getObjectInstance(Class<?> impl, Class<?>[] paramObj, Object[] valueObj) {
      try {
         Constructor<?> constr = impl.getConstructor(paramObj);
         return constr.newInstance(valueObj);
      } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException nsme) {
         DEBUG.error("SAML2SDKUtils.getObjectInstance: " + impl.getName(), nsme);
         return null;
      }
   }

   public static boolean checkStatement(Element element, String statementname) {
      if (element != null && statementname != null) {
         String tag = element.getLocalName();
         if (tag == null) {
            return false;
         }

         if (tag.equals("Statement")) {
            NamedNodeMap nm = element.getAttributes();
            int len = nm.getLength();
            String attrName = null;
            Attr attr = null;

            for (int j = 0; j < len; j++) {
               attr = (Attr)nm.item(j);
               attrName = attr.getLocalName();
               if (attrName != null && attrName.equals("type") && attr.getNodeValue().equals(statementname + "Type")) {
                  return true;
               }
            }

            return false;
         } else {
            return tag.equals(statementname);
         }
      } else {
         return false;
      }
   }

   public static String byteArrayToString(byte[] bytes) {
      char[] chars = new char[bytes.length];

      for (int i = 0; i < bytes.length; i++) {
         chars[i] = (char)bytes[i];
      }

      return new String(chars);
   }

   public static byte[] intToTwoBytes(int i) throws SAML2Exception {
      if (i >= 0 && i <= 65535) {
         String hexStr = Integer.toHexString(i);
         int len = hexStr.length();
         String norm;
         if (len > 4) {
            norm = hexStr.substring(0, 4);
         } else {
            norm = switch (len) {
               case 1 -> "000" + hexStr;
               case 2 -> "00" + hexStr;
               case 3 -> "0" + hexStr;
               default -> hexStr;
            };
         }

         return hexStringToByteArray(norm);
      } else {
         DEBUG.error("SAML2Utils.intToTwoBytes: wrong index value range.");
         throw new SAML2Exception(BUNDLE.getString("wrongInput"));
      }
   }

   public static int twoBytesToInt(byte[] bytes) throws SAML2Exception {
      if (bytes != null && bytes.length == 2) {
         String str0 = Integer.toHexString(bytes[0]);
         int len0 = str0.length();
         String norm0;
         if (len0 > 2) {
            norm0 = str0.substring(len0 - 2, len0);
         } else {
            norm0 = str0;
         }

         String str1 = Integer.toHexString(bytes[1]);
         int len1 = str1.length();
         String norm1;
         if (len1 > 2) {
            norm1 = str1.substring(len1 - 2, len1);
         } else if (len1 == 1) {
            norm1 = "0" + str1;
         } else {
            norm1 = str1;
         }

         String wholeHexStr = norm0 + norm1;
         return Integer.parseInt(wholeHexStr, 16);
      } else {
         DEBUG.error("SAML2Utils.twoBytesToInt: input is null or length is not 2.");
         throw new SAML2Exception(BUNDLE.getString("wrontInput"));
      }
   }

   public static String generateMessageHandle() {
      if (RANDOM == null) {
         return null;
      }

      byte[] bytes = new byte[20];
      RANDOM.nextBytes(bytes);
      return byteArrayToString(bytes);
   }

   public static byte[] stringToByteArray(String input) {
      char[] chars = input.toCharArray();
      byte[] bytes = new byte[chars.length];

      for (int i = 0; i < chars.length; i++) {
         bytes[i] = (byte)chars[i];
      }

      return bytes;
   }

   public static String byteArrayToHexString(byte[] byteArray) {
      StringBuilder hexData = new StringBuilder();

      for (byte b : byteArray) {
         int onebyte = 255 & b | -256;
         hexData.append(Integer.toHexString(onebyte).substring(6));
      }

      return hexData.toString();
   }

   private static byte[] hexStringToByteArray(String hexString) {
      int read = hexString.length();
      byte[] byteArray = new byte[read / 2];
      int i = 0;

      for (int j = 0; i < read; j++) {
         String part = hexString.substring(i, i + 2);
         byteArray[j] = Short.valueOf(Integer.toString(Integer.parseInt(part, 16))).byteValue();
         i++;
         i++;
      }

      return byteArray;
   }

   public static String generateID() {
      if (RANDOM == null) {
         return null;
      }

      byte[] bytes = new byte[20];
      RANDOM.nextBytes(bytes);
      return "s2" + byteArrayToHexString(bytes);
   }

   public static String fillInBasicAuthInfo(BaseConfigType config, String locationURL) {
      if (config == null) {
         return locationURL;
      }

      Map<String, List<String>> map = SAML2MetaUtils.getAttributes(config);
      List<String> baoList = map.get("basicAuthOn");
      if (baoList != null && !baoList.isEmpty()) {
         String on = baoList.getFirst();
         if (on == null) {
            return locationURL;
         }

         on = on.trim();
         if (!on.equalsIgnoreCase("true")) {
            return locationURL;
         }

         List<String> ul = map.get("basicAuthUser");
         if (ul != null && !ul.isEmpty()) {
            String u = ul.getFirst();
            if (u == null) {
               return locationURL;
            }

            u = u.trim();
            if (u.isEmpty()) {
               return locationURL;
            }

            List<String> pl = map.get("basicAuthPassword");
            String p = null;
            if (pl != null && !pl.isEmpty()) {
               p = pl.getFirst();
            }

            if (p == null) {
               p = "";
            }

            String dp = SAMLUtilsCommon.decodePassword(p);
            int index = locationURL.indexOf("//");
            return locationURL.substring(0, index + 2) + u + ":" + dp + "@" + locationURL.substring(index + 2);
         } else {
            return locationURL;
         }
      } else {
         return locationURL;
      }
   }

   public static Boolean stringToBoolean(String str) throws SAML2Exception {
      if (str == null) {
         return null;
      } else if (str.equals("true") || str.equals("1")) {
         return Boolean.TRUE;
      } else if (!str.equals("false") && !str.equals("0")) {
         throw new SAML2Exception(BUNDLE.getString("invalidXMLBooleanValue"));
      } else {
         return Boolean.FALSE;
      }
   }

   public static String removeDeployUri(String uri) {
      if (uri != null && !uri.isEmpty()) {
         int loc = uri.indexOf("/", 1);
         return loc == -1 ? null : uri.substring(loc);
      } else {
         return uri;
      }
   }

   public static Boolean booleanValueOf(String value) {
      return "true".equalsIgnoreCase(value) || "1".equals(value);
   }

   static {
      for (String iName : INTERFACE_NAMES) {
         try {
            String implClass = SystemPropertiesManager.get("com.sun.identity.saml2.sdk.mapping." + iName);
            if (implClass != null && !implClass.trim().isEmpty()) {
               if (DEBUG.messageEnabled()) {
                  DEBUG.message("SAML2SDKUtils.init: mapper for " + iName + "=" + implClass);
               }

               CLASS_MAPPING.put(iName, Class.forName(implClass.trim()));
            }
         } catch (ClassNotFoundException cnfe) {
            DEBUG.error("SAML2SDKUtils.init: " + iName, cnfe);
         }
      }
   }
}
