package com.airlock.iam.saml2.application.configuration.validation;

import com.airlock.iam.opensso.jaxb.entityconfig.EntityConfigElement;
import com.airlock.iam.opensso.jaxb.metadata.EntityDescriptorElement;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.valuereader.FilePropertyValueReader;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.lang3.StringUtils;

public final class Saml2MetadataValidator {
   private final PluginConfiguration config;
   private final ConfigurationEnvironment env;
   private final ConfigurationContext context;
   private final JAXBContext jaxbContext;

   public Saml2MetadataValidator(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) {
      this.config = config;
      this.env = env;
      this.context = context;

      try {
         this.jaxbContext = JAXBContext.newInstance("com.airlock.iam.opensso.jaxb.metadata:com.airlock.iam.opensso.jaxb.entityconfig");
      } catch (JAXBException e) {
         throw new IllegalStateException("Cannot create JAXBContext: " + e.getMessage(), e);
      }
   }

   public Saml2MetadataValidator validateStandardMetadataFile(String propertyName) throws ConfigurationException {
      this.validateMetadataFile(propertyName, "standard metadata", EntityDescriptorElement.class);
      return this;
   }

   public Saml2MetadataValidator validateExtendedMetadataFile(String propertyName) throws ConfigurationException {
      this.validateMetadataFile(propertyName, "extended metadata", EntityConfigElement.class);
      return this;
   }

   private void validateMetadataFile(String propertyName, String description, Class<?> expectedClass) throws ConfigurationException {
      if (this.context.isDefaultContext() || this.config.isPropertyDefinedForContext(propertyName, this.env, this.context)) {
         String path = FilePropertyValueReader.effectiveFilePropertyValue(this.config, propertyName, this.env, this.context);
         if (!StringUtils.isBlank(path)) {
            String contents;
            try {
               contents = Files.readString(Path.of(path), StandardCharsets.UTF_8);
            } catch (IOException e) {
               throw new PropertyConstraintViolationException(propertyName, this.config, this.env, this.context, "Unable to read file " + path);
            }

            try {
               Object obj = this.unmarshall(propertyName, contents);
               if (!expectedClass.isInstance(obj)) {
                  throw new PropertyConstraintViolationException(
                     propertyName,
                     this.config,
                     this.env,
                     this.context,
                     "File does not represent a valid " + description + " document. Instead found type: " + obj.getClass().getSimpleName()
                  );
               }
            } catch (RuntimeException | JAXBException e) {
               throw new PropertyConstraintViolationException(
                  propertyName, this.config, this.env, this.context, "Invalid " + description + ". Error message from parser: " + e.getMessage()
               );
            }
         }
      }
   }

   private Object unmarshall(String propertyName, String content) throws JAXBException {
      Unmarshaller u = this.jaxbContext.createUnmarshaller();
      return u.unmarshal(new StreamSource(new StringReader(content)));
   }
}
