package de.accso.flexinale.common.api.event;

import com.fasterxml.jackson.annotation.JsonSetter;
import de.accso.flexinale.common.shared_kernel.DeveloperMistakeException;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

@SuppressWarnings("CanBeFinal")
public abstract class AbstractEvent implements Event {

    public final Id id = Identifiable.Id.of();
    public final Id correlationId;
    @DoNotCheckInArchitectureTests
    protected LocalDateTime timestamp = LocalDateTime.now();
    @DoNotCheckInArchitectureTests
    protected final EventContext eventContext;

    // event is start of a new event chain
    protected AbstractEvent() {
        this.correlationId = Identifiable.Id.of();
        this.eventContext = new EventContext( this.correlationId,
                List.of( new EventContext.EventHistoryElement(id, this.getClass())) );
    }

    // event is part of an existing event chain but without a history
    protected AbstractEvent(final Id correlationId) {
        this.correlationId = correlationId;
        this.eventContext = new EventContext( this.correlationId,
                List.of( new EventContext.EventHistoryElement(id, this.getClass())) );
    }

    // event is part of an existing event chain
    protected AbstractEvent(final Event predecessorEvent) {
        this(predecessorEvent.eventContext());
    }

    // event is part of an existing event chain
    protected AbstractEvent(final EventContext predecessorEventContext) {
        this.correlationId = predecessorEventContext.correlationId;
        this.eventContext = new EventContext( this.correlationId,
                List.copyOf(predecessorEventContext.eventHistory.stream().toList()),
                new EventContext.EventHistoryElement(id, this.getClass())  // append this event to history
        );
    }

    @Override
    public Id id() {
        return id;
    }

    @Override
    public LocalDateTime timestamp() {
        return timestamp;
    }

    @Override
    public Id correlationId() {
        return correlationId;
    }

    @Override
    public EventContext eventContext() {
        return eventContext;
    }

    @JsonSetter("version") // needed as otherwise the static field version (in one of the subclasses) is not (de)serialized
    public void setVersion(Version newVersion) {
        String fieldName = "version";
        try {
            Field field = this.getClass().getDeclaredField(fieldName); // static field
            field.setAccessible(true);
            field.set(this, newVersion);
        }
        catch (NoSuchFieldException | IllegalAccessException ex) {
            throw new DeveloperMistakeException("version field cannot be set via reflection in an Event class", ex);
        }
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}

        final AbstractEvent that = (AbstractEvent) o;

        return Objects.equals(id, that.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }
}
