package example;

import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.util.List;

import org.hl7.fhir.instance.model.api.IBaseResource;

import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.auth.*;

public class AuthorizationInterceptors {

   public class PatientResourceProvider implements IResourceProvider
   {

      @Override
      public Class<? extends IBaseResource> getResourceType() {
         return Patient.class;
      }
      
      public MethodOutcome create(@ResourceParam Patient thePatient, RequestDetails theRequestDetails) {
         
         return new MethodOutcome(); // populate this
      }
      
   }
   
   //START SNIPPET: patientAndAdmin
   public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {
      
      @Override
      public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
         
         // Process authorization header - The following is a fake 
         // implementation. Obviously we'd want something more real
         // for a production scenario.
         // 
         // In this basic example we have two hardcoded bearer tokens,
         // one which is for a user that has access to one patient, and
         // another that has full access. 
         IdDt userIdPatientId = null;
         boolean userIsAdmin = false;
         String authHeader = theRequestDetails.getHeader("Authorization");
         if ("Bearer dfw98h38r".equals(authHeader)) {
            // This user has access only to Patient/1 resources
            userIdPatientId = new IdDt("Patient", 1L);
         } else if ("Bearer 39ff939jgg".equals(authHeader)) {
            // This user has access to everything
            userIsAdmin = true;
         } else {
            // Throw an HTTP 401
            throw new AuthenticationException("Missing or invalid Authorization header value");
         }

         // If the user is a specific patient, we create the following rule chain:
         // Allow the user to read anything in their own patient compartment
         // Allow the user to write anything in their own patient compartment
         // If a client request doesn't pass either of the above, deny it
         if (userIdPatientId != null) {
            return new RuleBuilder()
               .allow().read().allResources().inCompartment("Patient", userIdPatientId).andThen()
               .allow().write().allResources().inCompartment("Patient", userIdPatientId).andThen()
               .denyAll()
               .build();
         }
         
         // If the user is an admin, allow everything
         if (userIsAdmin) {
            return new RuleBuilder()
               .allowAll()
               .build();
         }
         
         // By default, deny everything. This should never get hit, but it's 
         // good to be defensive
         return new RuleBuilder()
            .denyAll()
            .build();
      }
   }
   //END SNIPPET: patientAndAdmin

   
   //START SNIPPET: conditionalUpdate
   @Update()
   public MethodOutcome update(
         @IdParam IdDt theId, 
         @ResourceParam Patient theResource, 
         @ConditionalUrlParam String theConditionalUrl, 
         RequestDetails theRequestDetails) {

      // If we're processing a conditional URL...
      if (isNotBlank(theConditionalUrl)) {
         
         // Pretend we've done the conditional processing. Now let's
         // notify the interceptors that an update has been performed
         // and supply the actual ID that's being updated
         IdDt actual = new IdDt("Patient", "1123");
         
         // There are a number of possible constructors for ActionRequestDetails.
         // You should supply as much detail about the sub-operation as possible
         IServerInterceptor.ActionRequestDetails subRequest = 
               new IServerInterceptor.ActionRequestDetails(theRequestDetails, actual);
         
         // Notify the interceptors
         subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE);
      }
      
      // In a real server, perhaps we would process the conditional 
      // request differently and follow a separate path. Either way,
      // let's pretend there is some storage code here.
      
      theResource.setId(theId.withVersion("2"));
      MethodOutcome retVal = new MethodOutcome();
      retVal.setCreated(true);
      retVal.setResource(theResource);
      return retVal;
   }
   //END SNIPPET: conditionalUpdate

   
}
