using System.Net.Mime;
using System.Xml;
using Autofac;
using FitConnect.Encryption;
using FitConnect.Interfaces.Subscriber;
using FitConnect.Models;
using FitConnect.Models.v1.Api;
using FitConnect.Services.Models;
using FitConnect.Services.Models.v1.Destination;
using FitConnect.Services.Models.v1.Submission;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using JsonSchema = NJsonSchema.JsonSchema;
using Metadata = FitConnect.Models.Api.Metadata.Metadata;
using SecurityEventToken = FitConnect.Models.SecurityEventToken;

namespace FitConnect;

/// <summary>
///     Fluent API for the FitConnect.Subscriber
/// </summary>
public class Subscriber : FitConnectClient, ISubscriberWithSubmission,
    ISubscriber {
    private IContainer? _container;

    internal Subscriber(IContainer container) : base(FitConnectEnvironment.Testing,
        container.Resolve<IFitConnectSettings>().SubscriberClientId,
        container.Resolve<IFitConnectSettings>().SubscriberClientSecret,
        container,
        container.Resolve<IFitConnectSettings>().PrivateKeyDecryption,
        container.Resolve<IFitConnectSettings>().PrivateKeySigning) {
        Encryption = new FitEncryption(
            container.Resolve<IFitConnectSettings>().PrivateKeyDecryption,
            container.Resolve<IFitConnectSettings>().PrivateKeySigning,
            container.Resolve<ILogger>());
        _container = container;
    }

    public Subscriber(FitConnectEnvironment environment,
        string clientId, string clientSecret,
        string privateKeyDecryption,
        string privateKeySigning,
        ILogger? logger = null) : base(environment, clientId, clientSecret, logger,
        privateKeyDecryption, privateKeySigning) {
        Encryption = new FitEncryption(privateKeyDecryption, privateKeySigning, logger);
    }


    /// <summary>
    ///     Receives the available submissions from the server
    /// </summary>
    /// <param name="destinationId"></param>
    /// <param name="skip"></param>
    /// <param name="take"></param>
    /// <returns></returns>
    public async Task<IEnumerable<SubmissionForPickupDto>> GetAvailableSubmissionsAsync(
        string? destinationId = null,
        int skip = 0,
        int take = 100) {
        var submissionsResult = await SubmissionService.ListSubmissionsAsync(destinationId, 0, 100);

        // Creating a dictionary of destinationId to submissionIds from the REST API result
        return submissionsResult.Submissions ?? new List<SubmissionForPickupDto>();
    }


    /// <summary>
    ///     Receives a specific submission from the server and verifies submission<br />
    ///     https://docs.fitko.de/fit-connect/docs/receiving/verification/
    /// </summary>
    /// <param name="submissionId"></param>
    /// <returns></returns>
    public async Task<ISubscriberWithSubmission> RequestSubmissionAsync(string submissionId) {
        var submission = (Submission)await SubmissionService.GetSubmissionAsync(submissionId);

        // SuccessCriteria:2.2, 2.3
        var (submitEvent, metadataSignature) = await CheckSecurityEventTokensAsync(submission);


        // SuccessCriteria:3.1
        if (metadataSignature != submission.EncryptedMetadata
                ?.Split(ProjectSpecification.TokenSeparator).Last()) {
            var problem = new Problems(Problems.ProblemTypeEnum.IncorrectAuthenticationTag,
                Problems.TitleAuthenticationTagInvalid,
                Problems.DetailAuthenticationMetadataInvalid,
                Problems.ProblemInstanceEnum.Metadata);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        string? metadataString;
        try {
            var (metaDataString, _, metaHash) =
                Encryption.Decrypt(submission.EncryptedMetadata!);
            metadataString = metaDataString;

            if (metadataString == null)
                throw new Exception("Unable to decrypt metadata");
        }
        catch (Exception e) {
            // SuccessCriteria:3.2
            var problem = new Problems(Problems.ProblemTypeEnum.EncryptionIssue,
                Problems.DetailDecryptionIssueMetadata);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem, e);
        }

        // SuccessCriteria:3.4
        submission.Metadata = JsonConvert.DeserializeObject<Metadata>(metadataString);
        if (submission.Metadata?.Schema == null) {
            var problem = new Problems(Problems.ProblemTypeEnum.MissingSchema,
                Problems.TitleMissingSchema,
                Problems.DetailMissingSchema,
                Problems.ProblemInstanceEnum.Metadata);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        JsonSchema? schema;
        try {
            schema = await JsonSchema.FromUrlAsync(submission.Metadata?.Schema);
        }
        catch (Exception e) {
            // SuccessCriteria:3.5
            var problem = new Problems(Problems.ProblemTypeEnum.UnsupportedMetaSchema,
                Problems.DetailUnsupportedSchema);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem, e);
        }

        await VerifyMetadata(submission, metadataString, schema);

        // SuccessCriteria:3.3
        if (submission.Metadata == null) {
            var problem = new Problems(Problems.ProblemTypeEnum.SyntaxViolation,
                Problems.DetailSyntaxViolationMetadata);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        // SuccessCriteria:3.8
        if (submission.Metadata.PublicServiceType.Identifier !=
            submission.ServiceType.Identifier) {
            var problem = new Problems(Problems.ProblemTypeEnum.ServiceMismatch,
                Problems.DetailServiceMismatch);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        // SuccessCriteria:3.9
        var destination = await DestinationService.GetDestinationAsync(submission.DestinationId);
        if (destination == null) throw new ArgumentException("Destination not found");

        var destinationService = destination.Services!.FirstOrDefault(s =>
            s.Identifier == submission.Metadata.PublicServiceType.Identifier);
        if (destinationService == null) {
            var problem = new Problems(Problems.ProblemTypeEnum.UnsupportedService,
                Problems.DetailUnsupportedService);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        await CheckReplyChannelAsync(submission, destination);

        string? dataString = null;
        if (submission.EncryptedData != null)
            try {
                (dataString, _, _) =
                    Encryption.Decrypt(submission.EncryptedData);
                submission.Data = dataString;

                if (dataString == null)
                    throw new Exception("DataString can not be null if decryption succeeded");
            }
            catch (Exception e) {
                // SuccessCriteria: 4.2
                var problem = new Problems(Problems.ProblemTypeEnum.EncryptionIssue,
                    Problems.DetailDecryptionIssueMetadata,
                    Problems.DetailDecryptionIssueData,
                    Problems.ProblemInstanceEnum.Data);
                await RejectSubmissionAsync(submission, problem);
                throw new SecurityEventException(problem, e);
            }

        // SuccessCriteria: 4.3 (Wrong problem?!)
        await VerifyDataHashAsync(submission, dataString!);

        // SuccessCriteria:3.10
        if (submission.Data == null) {
            var problem = new Problems(Problems.ProblemTypeEnum.MissingData,
                Problems.DetailMissingData);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }


        // SuccessCriteria:3.11
        if (submission.DataMimeType == MediaTypeNames.Application.Json) {
            var dataSchema = GetSchemaUrlFromJson(submission.Data);
            if (dataSchema == null)
                Logger?.LogWarning(
                    "$schema attribute missing in submission data of submission {SubmissionId}",
                    submission.Id);
        }


        if (destinationService.SubmissionSchemas != null && destinationService
                .SubmissionSchemas
                .Select(s => s.SchemaUri)
                .Contains(submission.Metadata.ContentStructure.Data.SubmissionSchema.SchemaUri)) {
            var problem = new Problems(Problems.ProblemTypeEnum.UnsupportedDataSchema,
                Problems.DetailUnsupportedDataSchema);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }


        // SuccessCriteria:4.4
        if (submission.DataMimeType == MediaTypeNames.Application.Json)
            if (JsonConvert.DeserializeObject(submission.Data) == null) {
                var problem = new Problems(
                    Problems.ProblemTypeEnum.SyntaxViolation,
                    Problems.TitleSyntaxViolation,
                    Problems.DetailSyntaxViolationDataJson,
                    Problems.ProblemInstanceEnum.Data);
                await RejectSubmissionAsync(submission, problem);
                throw new SecurityEventException(problem);
            }

        if (submission.DataMimeType == MediaTypeNames.Application.Xml)
            try {
                var doc = new XmlDocument();
                doc.LoadXml(submission.Data);
            }
            catch (Exception e) {
                var problem = new Problems(
                    Problems.ProblemTypeEnum.SyntaxViolation,
                    Problems.TitleSyntaxViolation,
                    Problems.DetailSyntaxViolationDataXml,
                    Problems.ProblemInstanceEnum.Data);
                await RejectSubmissionAsync(submission, problem);
                throw new SecurityEventException(problem, e);
            }

        submission.Attachments = await DownloadAttachmentsAsync(submission);
        // SuccessCriteria:2.4
        await CheckAttachments(submission, submitEvent);

        Submission = submission;
        return this;
    }

    public async Task RejectSubmissionAsync(ISubmitted submission, params Problems[] problems) {
        await CompleteSubmission(submission, FinishSubmissionStatus.Rejected, problems);
    }

    public Submission? Submission { get; private set; }


    /// <summary>
    ///     Reading attachments for a submission.
    /// </summary>
    /// <returns></returns>
    /// <summary>
    ///     Reading attachments for a submission.
    /// </summary>
    /// <returns></returns>
    public async Task<IEnumerable<Attachment>> GetAttachmentsAsync() {
        if (Submission?.Id == null || Submission?.Metadata == null)
            throw new Exception("No submission available");

        var attachments = new List<Attachment>();
        foreach (var id in Submission!.AttachmentIds) {
            var encryptedAttachment = await SubmissionService.GetAttachmentAsync(Submission.Id, id);
            var (_, content, hash) = Encryption.Decrypt(encryptedAttachment);
            var attachmentMeta =
                Submission.Metadata.ContentStructure.Attachments.First(a => a.AttachmentId == id);

            attachments.Add(new Attachment(id, attachmentMeta, content,
                encryptedAttachment.Split(ProjectSpecification.TokenSeparator).Last()) {
                Description = attachmentMeta.Description,
                Purpose = attachmentMeta.Purpose,
                MimeType = attachmentMeta.MimeType
            });
        }

        Submission.Attachments = attachments;
        return attachments;
    }

    public async Task AcceptSubmissionAsync() {
        await CompleteSubmissionAsync(Submission!, FinishSubmissionStatus.Accepted);
    }

    public async Task RejectSubmissionAsync(params Problems[] problems) {
        await CompleteSubmissionAsync(Submission!, FinishSubmissionStatus.Rejected, problems);
    }

    public bool VerifyStatus(AcceptanceStatus acceptanceStatus) {
        if (Submission == null)
            throw new NullReferenceException("Submission is null");
        var result = true;
        result &= acceptanceStatus.Metadata == Submission.MetaAuthentication;
        result &= acceptanceStatus.Data == Submission.DataAuthentication;
        if (Submission.Attachments != null)
            foreach (var attachment in Submission.Attachments)
                result &= acceptanceStatus.Attachments[attachment.Id] ==
                          attachment.AttachmentAuthentication;

        return result;
    }


    private string? GetSchemaUrlFromJson(string? jsonString) {
        return jsonString == null
            ? null
            : (JsonConvert.DeserializeObject(jsonString) as JObject)?["$schema"]?.ToString();
    }

    /// <summary>
    ///     Check Hash of attachment
    /// </summary>
    /// <param name="submission"></param>
    /// <returns></returns>
    /// <exception cref="SecurityEventException"></exception>
    private async Task<List<Attachment>> DownloadAttachmentsAsync(Submission submission) {
        var attachments = new List<Attachment>();
        foreach (var id in submission!.AttachmentIds) {
            string encryptedAttachment;
            try {
                encryptedAttachment = await SubmissionService.GetAttachmentAsync(submission.Id, id);
            }
            catch (Exception e) {
                var problem = new Problems(Problems.ProblemTypeEnum.MissingAttachments,
                    Problems.TitleAttachmentMissing,
                    string.Format(Problems.DetailAttachmentMissing, id),
                    $"attachment:{id}");
                await RejectSubmissionAsync(submission, problem);
                throw new SecurityEventException(problem, e);
            }

            byte[]? content;
            byte[]? hash;
            try {
                (_, content, hash) = Encryption.Decrypt(encryptedAttachment);
            }
            catch (Exception e) {
                var problem = new Problems(
                    Problems.ProblemTypeEnum.EncryptionIssue,
                    Problems.TitleDecryptionIssue,
                    string.Format(Problems.DetailDecryptionIssueAttachment, id),
                    $"attachment:{id}"
                );
                await RejectSubmissionAsync(submission, problem);
                throw new SecurityEventException(problem, e);
            }

            var attachmentMeta =
                submission.Metadata?.ContentStructure.Attachments.First(a =>
                    a.AttachmentId == id);

            if (attachmentMeta != null) {
                attachments.Add(new Attachment(id, attachmentMeta, content,
                    encryptedAttachment.Split(ProjectSpecification.TokenSeparator).Last()));

                // SuccessCriteria: Hash-Check 5.4
                if (attachmentMeta?.Hash.Content != FitEncryption.CalculateHash(content).Content) {
                    var problem = new Problems(
                        Problems.ProblemTypeEnum.HashMismatch,
                        Problems.TitleHashMismatch,
                        string.Format(Problems.DetailHashMismatchAttachment, id),
                        $"attachment:{id}");
                    await RejectSubmissionAsync(submission, problem);
                    throw new SecurityEventException(problem);
                }
            }
        }

        return attachments;
    }

    private async Task CompleteSubmission(FinishSubmissionStatus status) {
        await CompleteSubmission(Submission!, status);
    }

    private async Task CompleteSubmission(ISubmitted submission,
        FinishSubmissionStatus status, Problems[]? problems = null) {
        if (submission.SubmissionId == null || submission.CaseId == null ||
            submission.DestinationId == null)
            throw new ArgumentException("Submission does not contain all required fields");

        if (status != FinishSubmissionStatus.Rejected && problems != null)
            throw new ArgumentException("Problems can only be set for rejected submissions");

        string token;
        switch (status) {
            case FinishSubmissionStatus.Rejected:
                token = await Encryption.CreateRejectSecurityEventToken(submission.SubmissionId,
                    submission.CaseId, submission.DestinationId,
                    problems ??
                    throw new ArgumentException("On reject problems must be provided"));
                break;
            case FinishSubmissionStatus.Accepted:
                token = await Encryption.CreateAcceptSecurityEventToken(submission);
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(status), status, null);
        }

        Logger?.LogDebug("Token to accept submission: {Token}", token);
        var result = await CasesService.FinishSubmissionAsync(submission.CaseId, token);
        Logger?.LogInformation("Submission completed {Status}", result);
    }


    /// <summary>
    ///     Accept or reject submission
    /// </summary>
    private async Task CompleteSubmissionAsync(ISubmitted submission,
        FinishSubmissionStatus status, Problems[]? problems = null) {
        if (submission.SubmissionId == null || submission.CaseId == null ||
            submission.DestinationId == null)
            throw new ArgumentException("Submission does not contain all required fields");

        if (status != FinishSubmissionStatus.Rejected && problems != null)
            throw new ArgumentException("Problems can only be set for rejected submissions");

        // TODO Generate AuthenticationTags for Accept token

        string? token;
        switch (status) {
            case FinishSubmissionStatus.Rejected:
                token = await Encryption.CreateRejectSecurityEventToken(submission.SubmissionId,
                    submission.CaseId, submission.DestinationId,
                    problems ?? throw new ArgumentException("On reject problems must be provided"));
                break;
            case FinishSubmissionStatus.Accepted:
                if (submission is Submission sub) {
                    var authenticationTags =
                        FitEncryption.GenerateAuthenticationTags(sub);
                    token = await Encryption.CreateAcceptSecurityEventToken(submission,
                        authenticationTags);
                }
                else {
                    throw new ArgumentException("Only full qualified submission allowed to accept");
                }

                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(status), status, null);
        }

        Logger?.LogDebug("Token to accept submission: {Token}", token);
        var result = await CasesService.FinishSubmissionAsync(submission.CaseId, token);
        Logger?.LogInformation("Submission completed {Status}", result);
    }

    public static string VerifyCallback(string callbackSecret,
        long timestamp, string body) {
        if (timestamp < DateTime.Now.AddMinutes(ProjectSpecification.MaxCallbackAge * -1)
                .ToEpochTime())
            throw new ArgumentException("Request is too old");
        var hmac = ProjectSpecification.CalculateCallbackHmac(callbackSecret, timestamp, body);
        return Convert.ToHexString(hmac).ToLower();
    }


    public static bool VerifyCallback(string callbackSecret, HttpRequest request) {
        if (!request.Headers.ContainsKey(ProjectSpecification.CallbackTimestamp))
            throw new ArgumentException("Missing callback-timestamp header");

        var timeStampString = request.Headers[ProjectSpecification.CallbackTimestamp].ToString();
        if (!long.TryParse(timeStampString, out var timestamp))
            throw new ArgumentException("Invalid callback-timestamp header");


        var authentication = request.Headers[ProjectSpecification.CallbackAuthentication];
        using var requestStream = request.Body;
        var content = new StreamReader(requestStream).ReadToEnd().Trim();

        var result = VerifyCallback(callbackSecret, timestamp, content);
        if (result != authentication)
            throw new ArgumentException("Verified request does not match authentication");
        return true;
    }

    private enum FinishSubmissionStatus {
        Accepted,
        Rejected
    }

    #region Check Submission and Reject if needed

    private async Task CheckDataSchemaAsync(string dataSchema, Submission submission) {
        var dataSchemaObject = JsonSchema.FromUrlAsync(dataSchema).Result;
        var jSchema = JSchema.Parse(dataSchemaObject.ToJson());
        var isValidSchemaData = JObject.Parse(submission.Data!).IsValid(jSchema);

        if (!isValidSchemaData) {
            var problem = new Problems(Problems.ProblemTypeEnum.SchemaViolation,
                Problems.DetailsSchemaViolationData);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }
    }


    private async Task VerifyDataHashAsync(Submission submission, string dataString) {
        if (submission.Metadata?.ContentStructure.Data.Hash.Content ==
            FitEncryption.CalculateHash(dataString).Content) return;

        Logger?.LogWarning("Data hash mismatch: {DataHash} != {CalculatedHash}",
            submission.Metadata?.ContentStructure.Data.Hash.Content,
            FitEncryption.CalculateHash(dataString));

        // SuccessCriteria: 4.3
        var problem = new Problems(Problems.ProblemTypeEnum.HashMismatch,
            Problems.TitleHashMismatch,
            Problems.DetailHashMismatchData, Problems.ProblemInstanceEnum.Data);
        await RejectSubmissionAsync(submission, problem);
        throw new SecurityEventException(problem);
    }


    // SuccessCriteria:3
    /// <summary>
    ///     Checking metadata
    /// </summary>
    /// <param name="submission"></param>
    /// <param name="metaDataString"></param>
    /// <param name="schema"></param>
    /// <exception cref="Exception"></exception>
    private async Task VerifyMetadata(Submission submission, string metaDataString,
        JsonSchema schema) {
        var valid = await JsonHelper.VerifyMetadata(metaDataString, schema);
        if (!valid) {
            Logger?.LogWarning("Invalid metadata: {MetaData}", metaDataString);

            // SuccessCriteria:3.6
            var problem = new Problems(Problems.ProblemTypeEnum.UnsupportedMetaSchema,
                Problems.DetailUnsupportedSchema);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }
    }


    /// <summary>
    ///     Compares the reply channel of the submission with the allowed channels of the destination
    /// </summary>
    /// <param name="submission"></param>
    /// <param name="destination"></param>
    /// <exception cref="SecurityEventException"></exception>
    private async Task CheckReplyChannelAsync(Submission submission,
        PublicDestinationDto destination) {
        var rcSubmission = submission.Metadata?.ReplyChannel;
        var rcDestination = destination.ReplyChannels;

        if (rcSubmission == null) return;

        if (rcSubmission.Elster != null && rcDestination?.Elster != null) return;
        if (rcSubmission.Fink != null && rcDestination?.Fink != null) return;
        if (rcSubmission.DeMail != null && rcDestination?.DeMail != null) return;
        if (rcSubmission.EMail != null && rcDestination?.EMail != null) return;

        var problems = new Problems(Problems.ProblemTypeEnum.UnsupportedReplyChannel,
            Problems.DetailUnsupportedReplyChannel);
        await RejectSubmissionAsync(submission, problems);
        throw new SecurityEventException(problems);
    }

    /// <summary>
    ///     Checking:<br />
    ///     - Exactly one submit element<br />
    ///     - Authentication Tag of submit event<br />
    ///     - Authentication Tag of submission data
    /// </summary>
    /// <param name="submission"></param>
    /// <returns></returns>
    /// <exception cref="SecurityEventException"></exception>
    private async Task<(SecurityEventToken, string)> CheckSecurityEventTokensAsync(
        Submission submission) {
        List<SecurityEventToken> status;
        try {
            status = await GetStatusForSubmissionAsync(submission);
        }
        catch (Exception? e) {
            Logger?.LogWarning("Could not get status for submission: {SubmissionId}",
                submission.Id);
            await RejectSubmissionAsync(submission,
                new Problems(Problems.ProblemTypeEnum.InvalidEventLog,
                    Problems.DetailEventLogInconsistent));

            throw new SecurityEventException(Problems.TitleEventLogInconsistent,
                Problems.DetailEventLogInconsistent, e);
        }

        if (status.Where(s => s.SubmissionId == submission.Id)
                .Count(set => set.EventType == EventType.Submit) != 1) {
            await RejectSubmissionAsync(submission,
                new Problems(Problems.ProblemTypeEnum.InvalidEventLog,
                    Problems.DetailEventLogNotExactlyOneSubmit));
            throw new SecurityEventException(Problems.TitleEventLogInconsistent,
                Problems.DetailEventLogNotExactlyOneSubmit);
        }

        var submitEvent = status.First(set => set.EventType == EventType.Submit);
        var authenticationTag = (submitEvent.Events?.SubmitSubmissionEvent?.AuthenticationTags);

        var dataSignature = authenticationTag?.Data;
        var metadataSignature = authenticationTag?.Metadata ?? "";

        if (submission.EncryptedData?.Split(ProjectSpecification.TokenSeparator).Last() !=
            dataSignature) {
            // SuccessCriteria: 4.1
            var problem = new Problems(Problems.ProblemTypeEnum.IncorrectAuthenticationTag,
                Problems.DetailHashMismatchData);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        if (submission.EncryptedMetadata?.Split(ProjectSpecification.TokenSeparator).Last() !=
            metadataSignature) {
            var problem = new Problems(Problems.ProblemTypeEnum.IncorrectAuthenticationTag,
                Problems.DetailAuthenticationMetadataInvalid);
            await RejectSubmissionAsync(submission, problem);
            throw new SecurityEventException(problem);
        }

        return (submitEvent, metadataSignature);
    }

    /// <summary>
    ///     Checking Attachments<br />
    ///     - Verify list of attachments<br />
    ///     - Authentication tags of attachments
    /// </summary>
    /// <exception cref="SecurityEventException"></exception>
    private async Task CheckAttachments(Submission submission,
        SecurityEventToken submitEvent) {
        if (submission?.Attachments != null) {
            // @formatter:off
            // SuccessCriteria::3.12 && 2.4 
            if (submission.Attachments.Count != submission.Metadata?.ContentStructure.Attachments.Count 
                || submission.Attachments.Count != (submitEvent.Events?.SubmitSubmissionEvent?.AuthenticationTags?.Attachments?.Count ?? 0)
                || !submission.Attachments.TrueForAll(a =>submission.Metadata?.ContentStructure.Attachments?.Any(ma=>ma.AttachmentId == a.Id) ?? false)
                || !submission.Attachments.TrueForAll(a =>submitEvent.Events?.SubmitSubmissionEvent?.AuthenticationTags?.Attachments?.ContainsKey(a.Id) ?? false)
                ) {
                var problem = new Problems(Problems.ProblemTypeEnum.AttachmentMismatch,
                    Problems.DetailAttachmentsMismatch);
                await RejectSubmissionAsync(submission, problem);
                throw new SecurityEventException(problem);
            }
            // @formatter:on

            var attachmentAuthenticationTags =
                submitEvent.Events!.SubmitSubmissionEvent!.AuthenticationTags!.Attachments;
            // SuccessCriteria:5.2
            var problems = new List<Problems>();
            foreach (var attachment in submission.Attachments)
                if (attachmentAuthenticationTags?[attachment.Id] !=
                    attachment.AttachmentAuthentication) {
                    var problem = new Problems(Problems.ProblemTypeEnum.IncorrectAuthenticationTag,
                        string.Format(Problems.DetailAuthenticationAttachmentInvalid,
                            attachment.Id));
                    problems.Add(problem);
                }

            if (problems.Any()) {
                await RejectSubmissionAsync(submission, problems.ToArray());
                throw new SecurityEventException(problems.ToArray());
            }
        }
    }

    #endregion
}