/*
 * Copyright 2020, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.testing.server.blackbox;

import com.google.common.flogger.FluentLogger.Api;
import com.google.common.flogger.StackSize;
import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;
import io.spine.base.EventMessage;
import io.spine.base.Identifier;
import io.spine.core.MessageId;
import io.spine.core.Subscribe;
import io.spine.logging.Logging;
import io.spine.server.event.AbstractEventSubscriber;
import io.spine.system.server.AggregateHistoryCorrupted;
import io.spine.system.server.CannotDispatchDuplicateCommand;
import io.spine.system.server.CannotDispatchDuplicateEvent;
import io.spine.system.server.ConstraintViolated;
import io.spine.system.server.HandlerFailedUnexpectedly;
import io.spine.system.server.RoutingFailed;

import java.io.PrintStream;

import static io.spine.json.Json.toJson;
import static java.lang.String.format;

/**
 * A subscriber for all the diagnostic events.
 *
 * <p>Logs all the received diagnostic events with a meaningful message.
 */
final class DiagnosticLog
        extends AbstractEventSubscriber
        implements Logging {

    private static final DiagnosticLog instance = new DiagnosticLog();

    /**
     * Obtains the only instance of {@code DiagnosticLog}.
     */
    static DiagnosticLog instance() {
        return instance;
    }

    /**
     * Prevents direct instantiation.
     */
    private DiagnosticLog() {
        super();
    }

    @Subscribe
    void on(ConstraintViolated event) {
        String typeUrl = event.getEntity()
                              .getTypeUrl();
        String idAsString = Identifier.toString(event.getEntity().getId());
        log(event, "Entity state (ID: `%s`, type: `%s`) is invalid.", idAsString, typeUrl);
    }

    @Subscribe
    void on(CannotDispatchDuplicateCommand event) {
        MessageId command = event.getDuplicateCommand();
        log(event, "Command `%s` (ID: `%s`) should not be dispatched twice.",
            command.getTypeUrl(),
            command.asCommandId()
                   .getUuid());
    }

    @Subscribe
    void on(CannotDispatchDuplicateEvent event) {
        MessageId duplicateEvent = event.getDuplicateEvent();
        log(event, "Event `%s` (ID: `%s`) should not be dispatched twice.",
            duplicateEvent.getTypeUrl(),
            duplicateEvent.asEventId()
                          .getValue());
    }

    @Subscribe
    void on(HandlerFailedUnexpectedly event) {
        log(event, "Signal `%s` could not be handled by %s:%n%s%n",
            event.getHandledSignal()
                 .getTypeUrl(),
            event.getEntity()
                 .getTypeUrl(),
            event.getError()
                 .getMessage());
    }

    @Subscribe
    void on(RoutingFailed event) {
        log(event, "Signal `%s` could not be routed to `%s`:%n%s%n",
            event.getHandledSignal()
                 .getTypeUrl(),
            event.getEntityType()
                 .getJavaClassName(),
            event.getError()
                 .getMessage());
    }

    @Subscribe
    void on(AggregateHistoryCorrupted event) {
        MessageId aggregate = event.getEntity();
        String idAsString = Identifier.toString(aggregate.getId());
        log(event, "History of aggregate `%s` (ID: `%s`) could not be loaded:%n%s%n",
            aggregate.getTypeUrl(),
            idAsString,
            event.getError()
                 .getMessage());
    }

    @FormatMethod
    private void log(EventMessage event, @FormatString String errorMessage, Object... formatArgs) {
        String msg = format(errorMessage, formatArgs);
        Api severeLogger = logger()
                .atSevere()
                .withStackTrace(StackSize.NONE);
        boolean loggingEnabled = severeLogger.isEnabled();
        if (loggingEnabled) {
            severeLogger.log(msg);
            severeLogger.log(toJson(event));
        } else {
            @SuppressWarnings("UseOfSystemOutOrSystemErr")
                // Edge case for disabled/misconfigured logging .
            PrintStream stderr = System.err;
            stderr.println(msg);
            stderr.println(toJson(event));
        }
    }
}

@@ -2534,22 +2534,24 @@
     
-String typeUrl
+MessageId entity
  = e
@@ -2558,32 +2558,33 @@
 vent.getEntity()
+;
 %0A               
@@ -2576,38 +2576,39 @@
         
-                      
+String typeUrl = entity
 .getType
@@ -2663,32 +2663,21 @@
 String(e
-vent.getE
 ntity
-()
 .getId()
@@ -2703,22 +2703,19 @@
 t, %22
-Entity
+The
  state (
 ID: 
@@ -2714,22 +2714,37 @@
 te (
-ID
+type
 : %60%25s%60
-, type
+) of the entity (ID
 : %60%25
@@ -2760,16 +2760,37 @@
 valid.%22,
+%0A            typeUrl,
  idAsStr
@@ -2796,17 +2796,8 @@
 ring
-, typeUrl
 );%0A 
@@ -2946,17 +2946,21 @@
 event, %22
-C
+The c
 ommand %60
@@ -3523,35 +3523,52 @@
 log(event, %22
-Signal
+The entity (state type
  %60%25s%60
+)
  could not b
@@ -3570,24 +3570,30 @@
 not 
-be 
 handle
-d by 
+ the signal %60
 %25s
+%60
 :%25n%25
@@ -3615,37 +3615,30 @@
    event.get
-HandledSignal
+Entity
 ()%0A         
@@ -3673,38 +3673,45 @@
        event.get
-Entity
+HandledSignal
 ()%0A             
@@ -3868,17 +3868,21 @@
 event, %22
-S
+The s
 ignal %60%25
@@ -4332,11 +4332,42 @@
 ent,
- %22H
+%0A            %22Unable to load the h
 isto
@@ -4372,16 +4372,20 @@
 tory of 
+the 
 aggregat
@@ -4385,23 +4385,36 @@
 ggregate
+ (state type:
  %60%25s%60
+,
  
-(
 ID: %60%25s%60
@@ -4418,28 +4418,8 @@
 %25s%60)
- could not be loaded
 :%25n%25

Improve error messages