package com.airlock.iam.common.infrastructure.csp;

import com.airlock.iam.common.domain.service.csp.ContentSecurityPolicy;
import com.airlock.iam.common.domain.service.csp.ContentSecurityPolicyProcessor;
import com.airlock.iam.common.domain.service.csp.ContentSecurityPolicyService;
import com.airlock.iam.common.domain.service.csp.CspNonce;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;

public final class ContentSecurityPolicyServiceImpl implements ContentSecurityPolicyService {
   private static final String CSP_NONCE_REQUEST_ATTRIBUTE = "cspNonce";
   private static final String CONTENT_SECURITY_POLICY_HEADER_NAME = "Content-Security-Policy";
   private final HttpServletRequest request;
   private final HttpServletResponse response;
   private final Optional<ContentSecurityPolicy> contentSecurityPolicy;
   private final List<ContentSecurityPolicyProcessor> processors;

   @Inject
   public ContentSecurityPolicyServiceImpl(
      HttpServletRequest request,
      HttpServletResponse response,
      Optional<ContentSecurityPolicy> contentSecurityPolicy,
      List<ContentSecurityPolicyProcessor> processors
   ) {
      this.request = request;
      this.response = response;
      this.contentSecurityPolicy = contentSecurityPolicy;
      this.processors = processors;
   }

   public static Optional<String> cspNonce(HttpServletRequest request) {
      Object nonce = request.getAttribute("cspNonce");
      return nonce instanceof String ? Optional.of((String)nonce) : Optional.empty();
   }

   @Override
   public void applyPolicy() {
      this.contentSecurityPolicy.ifPresent(this::applyPolicy);
   }

   private void applyPolicy(ContentSecurityPolicy policy) {
      CspNonce nonce = CspNonce.generateCspNonce();
      this.request.setAttribute("cspNonce", nonce.nonce());
      this.response.setHeader("Content-Security-Policy", this.renderPolicy(nonce, policy));
   }

   private String renderPolicy(CspNonce nonce, ContentSecurityPolicy csp) {
      String policy = csp.render(nonce);
      return this.processPolicy(policy);
   }

   private String processPolicy(String policy) {
      for (ContentSecurityPolicyProcessor processor : this.processors) {
         policy = processor.process(policy);
      }

      return policy;
   }
}
