﻿// ###################### | #############################################################
// CLASS ................ | SOFTWARE DEVELOPMENT CAPSTONE (C868) ........................
// ASSIGNMENT ........... | C# PERFORMANCE ASSESSMENT ...................................
// TASK ................. | EZP1 TASK 2: SOFTWARE APPLICATION AND DOCUMENTATION .........
// ###################### | #############################################################
// WGU STUDENT .......... | Mark K Ewer .................................................
// STUDENT ID ........... | 001094518 ...................................................
// DEGREE PROGRAM ....... | B.S. in Software Development ................................
// ENROLLED ON .......... | Feb 1, 2019 .................................................
// MENTOR ............... | Becky Stovall ...............................................
// PHONE ................ | (904) 238-7347 ..............................................
// TIME ZONE ............ | Eastern Time ................................................
// EMAIL ................ | MEwer@wgu.edu or Mark@3w3r.com ..............................
// WEB SITE ............. | http://www.markewer.com .....................................
// ###################### | #############################################################
namespace Ewer.Cqrs.Events
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Ewer.Cqrs.Extensions;

    /// <summary>
    ///   Provides a bunch of static methods that make it easy to perform some simple
    ///   integrity tests on an event batch that is to be committed
    /// </summary>
    public class EventValidation
    {
        /// <summary>
        ///   Validates the integrity of the given event batch with respect to sequence
        ///   numbers etc.
        /// </summary>
        public static void ValidateBatchIntegrity(Guid batchId, List<EventData> events)
        {
            EnsureAllEventsHaveSequenceNumbers(events);

            EnsureAllEventsHaveAggregateRootId(events);

            EnsureSeq(batchId, events);
        }

        private static void EnsureAllEventsHaveAggregateRootId(List<EventData> events)
        {
            if (events.Any(e => !e.Meta.ContainsKey(DomainEvent.MetadataKeys.AggregateRootId)))
            {
                throw new InvalidOperationException("Can't save batch with event without an aggregate root id");
            }
        }

        private static void EnsureAllEventsHaveSequenceNumbers(List<EventData> events)
        {
            if (events.Any(e => !e.Meta.ContainsKey(DomainEvent.MetadataKeys.SequenceNumber)))
            {
                throw new InvalidOperationException("Can't save batch with event without a sequence number");
            }

            if (events.Any(e => !e.Meta.ContainsKey(DomainEvent.MetadataKeys.GlobalSequenceNumber)))
            {
                throw new InvalidOperationException("Can't save batch with event without a global sequence number");
            }
        }

        private static void EnsureSeq(Guid batchId, List<EventData> events)
        {
            var aggregateRootSeqs = events
                .GroupBy(e => e.GetAggregateRootId())
                .ToDictionary(g => g.Key, g => g.Min(e => e.GetSequenceNumber()));

            var expectedNextGlobalSeq = events.First().GetGlobalSequenceNumber();

            foreach (var e in events)
            {
                var sequenceNumberOfThisEvent = e.GetSequenceNumber();
                var globalSequenceNumberOfThisEvent = e.GetGlobalSequenceNumber();
                var aggregateRootId = e.GetAggregateRootId();
                var expectedSequenceNumber = aggregateRootSeqs[aggregateRootId];

                if (globalSequenceNumberOfThisEvent != expectedNextGlobalSeq)
                {
                    throw new InvalidOperationException(
                        string.Format(@"Attempted to save batch {0} which contained events with non-sequential global sequence numbers!

{1}", batchId,
                            string.Join(", ", events.Select(ev => ev.GetGlobalSequenceNumber()))));
                }

                if (sequenceNumberOfThisEvent != expectedSequenceNumber)
                {
                    throw new InvalidOperationException(
                        string.Format(@"Attempted to save batch {0} which contained events with non-sequential sequence numbers!

{1}", batchId,
                            string.Join(Environment.NewLine,
                                events.Select(
                                    ev => string.Format("    {0} / {1}", ev.GetAggregateRootId(), ev.GetSequenceNumber())))));
                }

                aggregateRootSeqs[aggregateRootId]++;
                expectedNextGlobalSeq++;
            }
        }
    }
}
