/*
 * Copyright 2018 the original author or authors.
 *
 *  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 org.openehealth.ipf.commons.ihe.fhir.audit.events;

import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import org.openehealth.ipf.commons.audit.AuditContext;
import org.openehealth.ipf.commons.audit.codes.EventActionCode;
import org.openehealth.ipf.commons.audit.codes.ParticipantObjectIdTypeCode;
import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCode;
import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole;
import org.openehealth.ipf.commons.audit.event.CustomAuditMessageBuilder;
import org.openehealth.ipf.commons.ihe.core.atna.event.IHEAuditMessageBuilder;
import org.openehealth.ipf.commons.ihe.fhir.audit.GenericFhirAuditDataset;
import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventIdCode;
import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode;
import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * Builder for audit events related to generic FHIR transactions, using the resource type
 * and operation type for the participants. The audit messages follow the pattern shared
 * with audit messages of IHE transactions. Some exceptions:
 * <ul>
 * <li>the Event ID is always EV("rest", "http://hl7.org/fhir/audit-event-type", "RESTful Operation") (as described
 * in https://www.hl7.org/fhir/valueset-audit-event-type.html)</li>
 * <li>and the Event Action corresponds with the FHIR operation (see https://www.hl7.org/fhir/valueset-audit-event-action.html)</li>
 * <li>the query/resource participant object's ParticipantObjectIdType is the resource type</li>
 * </ul>
 * This also allows an easy conversion into a FHIR AuditEvent.
 *
 * @author Christian Ohr
 * @since 3.5
 */
public class GenericFhirAuditMessageBuilder extends
    IHEAuditMessageBuilder<GenericFhirAuditMessageBuilder, CustomAuditMessageBuilder> {

    public GenericFhirAuditMessageBuilder(AuditContext auditContext, GenericFhirAuditDataset auditDataset) {
        super(auditContext, auditDataset, new CustomAuditMessageBuilder(
            auditDataset.getEventOutcomeIndicator(),
            auditDataset.getEventOutcomeDescription(),
            eventActionCode(auditDataset.getOperation()),
            FhirEventIdCode.RestfulOperation,
            FhirEventTypeCode.fromRestOperationType(auditDataset.getOperation())
        ));

        // First the source, then the destination
        if (auditDataset.isServerSide()) {
            setRemoteParticipant(auditDataset);
            addHumanRequestor(auditDataset);
            setLocalParticipant(auditDataset);
        } else {
            setLocalParticipant(auditDataset);
            addHumanRequestor(auditDataset);
            setRemoteParticipant(auditDataset);
        }
    }

    public GenericFhirAuditMessageBuilder addPatients(GenericFhirAuditDataset auditDataset) {
        if (auditDataset.getPatientIds() != null)
            auditDataset.getPatientIds().stream()
                .filter(Objects::nonNull)
                .forEach(patientId ->
                    delegate.addParticipantObjectIdentification(
                        ParticipantObjectIdTypeCode.PatientNumber,
                        null,
                        null,
                        null,
                        patientId,
                        ParticipantObjectTypeCode.Person,
                        ParticipantObjectTypeCodeRole.Patient,
                        null,
                        null));
        return self();
    }

    /**
     * @param auditDataset Audit Dataset
     * @return this
     */
    public GenericFhirAuditMessageBuilder addQueryParticipantObject(GenericFhirAuditDataset auditDataset) {
        delegate.addParticipantObjectIdentification(
            FhirParticipantObjectIdTypeCode.fromResourceType(
                auditDataset.getAffectedResourceType() != null ?
                    auditDataset.getAffectedResourceType() :
                    getAuditContext().getAuditValueIfMissing()),
            null,
            auditDataset.getQueryString().getBytes(StandardCharsets.UTF_8),
            null,
            "FHIR Restful Query",
            ParticipantObjectTypeCode.System,
            ParticipantObjectTypeCodeRole.Query,
            null,
            null);

        return self();
    }

    /**
     * @param auditDataset Audit Dataset
     * @return this
     */
    public GenericFhirAuditMessageBuilder addResourceParticipantObject(GenericFhirAuditDataset auditDataset) {
        delegate.addParticipantObjectIdentification(
            FhirParticipantObjectIdTypeCode.fromResourceType(
                auditDataset.getAffectedResourceType() != null ?
                    auditDataset.getAffectedResourceType() :
                    getAuditContext().getAuditValueIfMissing()),
            null,
            null,
            null,
            auditDataset.getResourceId().getIdPart(),
            ParticipantObjectTypeCode.System,
            ParticipantObjectTypeCodeRole.Job,
            null,
            auditDataset.getSecurityLabel());
        return self();
    }

    public GenericFhirAuditMessageBuilder addJwtParticipants(GenericFhirAuditDataset auditDataset) {
        BalpJwtUtils.addJwtParticipant(delegate, auditDataset, getAuditContext());
        return self();
    }


    private static EventActionCode eventActionCode(RestOperationTypeEnum operation) {
        if (operation == null)
            return EventActionCode.Execute;
        return switch (operation) {
            case CREATE -> EventActionCode.Create;
            case READ, VREAD -> EventActionCode.Read;
            case UPDATE, PATCH -> EventActionCode.Update;
            case DELETE -> EventActionCode.Delete;
            default -> EventActionCode.Execute;
        };
    }
}
