/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.causeway.extensions.commandlog.jpa.dom;

import java.util.UUID;

import jakarta.inject.Named;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.Lob;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.apache.causeway.applib.annotation.Domain;
import org.apache.causeway.applib.annotation.DomainObject;
import org.apache.causeway.applib.annotation.Editing;
import org.apache.causeway.applib.annotation.Publishing;
import org.apache.causeway.applib.jaxb.PersistentEntityAdapter;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry.Nq;
import org.apache.causeway.persistence.jpa.applib.integration.CausewayEntityListener;
import org.apache.causeway.persistence.jpa.integration.typeconverters.applib.CausewayBookmarkConverter;
import org.apache.causeway.persistence.jpa.integration.typeconverters.java.util.JavaUtilUuidConverter;
import org.apache.causeway.persistence.jpa.integration.typeconverters.schema.v2.CausewayCommandDtoConverter;
import org.apache.causeway.schema.cmd.v2.CommandDto;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Table(
        schema = CommandLogEntry.SCHEMA,
        name = CommandLogEntry.TABLE,
        indexes = {
                @Index(name = "CommandLogEntry__startedAt_timestamp__IDX", columnList = "startedAt, timestamp" ),
                @Index(name = "CommandLogEntry__timestamp__IDX", columnList = "timestamp"),
                @Index(name = "CommandLogEntry__target__IDX", columnList = "target"),
                @Index(name = "CommandLogEntry__target_startedAt__IDX", columnList = "target, startedAt"),
        }
)
@NamedQueries({
    @NamedQuery(
            name  = Nq.FIND_BY_INTERACTION_ID,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.pk.interactionId = :interactionId"),
    @NamedQuery(
            name  = Nq.FIND_RECENT_BY_TARGET,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.target = :target "
                  + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 30
    @NamedQuery(
            name  = Nq.FIND_RECENT_BY_TARGET_OR_RESULT,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.target = :targetOrResult "
                  + "    OR cl.result = :targetOrResult "
                  + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 30
    @NamedQuery(
            name  = Nq.FIND_BY_TARGET_AND_TIMESTAMP_BETWEEN,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.target = :target "
                  + "   AND cl.timestamp >= :from "
                  + "   AND cl.timestamp <= :to "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_BY_TARGET_AND_TIMESTAMP_AFTER,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.target = :target "
                  + "  AND cl.timestamp >= :from "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_BY_TARGET_AND_TIMESTAMP_BEFORE,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.target = :target "
                  + "   AND cl.timestamp <= :to "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_BY_TARGET,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.target = :target "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_BY_TIMESTAMP_BETWEEN,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.timestamp >= :from "
                  + "   AND  cl.timestamp <= :to "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_BY_TIMESTAMP_AFTER,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.timestamp >= :from "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_BY_TIMESTAMP_BEFORE,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.timestamp <= :to "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name = Nq.FIND_MOST_RECENT,
            query = "SELECT cl "
                    + "  FROM CommandLogEntry cl "
                    + " ORDER BY cl.timestamp DESC, cl.pk.interactionId DESC"), // programmatic LIMIT 30
    @NamedQuery(
            name  = Nq.FIND_RECENT_BY_USERNAME,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.username = :username "
                  + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 30
        @NamedQuery(
                name  = Nq.FIND_BY_PARENT_INTERACTION_ID,
                query = "SELECT cl "
                        + "  FROM CommandLogEntry cl "
                        + " WHERE cl.parentInteractionId = :parentInteractionId "),
        @NamedQuery(
                name  = Nq.FIND_CURRENT,
                query = "SELECT cl "
                        + "  FROM CommandLogEntry cl "
                        + " WHERE cl.completedAt is null "
                        + " ORDER BY cl.timestamp DESC"),
        @NamedQuery(
                name  = Nq.FIND_COMPLETED,
                query = "SELECT cl "
                        + "  FROM CommandLogEntry cl "
                        + " WHERE cl.completedAt is not null "
                        + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_FIRST,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.startedAt   is not null "
                  + "   AND cl.completedAt is not null "
                  + " ORDER BY cl.timestamp ASC"), // programmatic LIMIT 1
    @NamedQuery(
            name  = Nq.FIND_SINCE,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.timestamp > :timestamp "
                  + "   AND cl.startedAt is not null "
                  + "   AND cl.completedAt is not null "
                  + " ORDER BY cl.timestamp ASC"),
    @NamedQuery(
            name  = Nq.FIND_BACKGROUND_AND_NOT_YET_STARTED,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.executeIn = org.apache.causeway.extensions.commandlog.applib.dom.ExecuteIn.BACKGROUND "
                  + "   AND cl.startedAt is null "
                  + " ORDER BY cl.timestamp ASC"),
        @NamedQuery(
            name  = Nq.FIND_RECENT_BACKGROUND_BY_TARGET,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.executeIn = org.apache.causeway.extensions.commandlog.applib.dom.ExecuteIn.BACKGROUND "
                  + "   AND cl.target    = :target "
                  + " ORDER BY cl.timestamp DESC"),
    @NamedQuery(
            name  = Nq.FIND_MOST_RECENT_REPLAYED,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE (cl.replayState = org.apache.causeway.extensions.commandlog.applib.dom.ReplayState.OK OR cl.replayState = org.apache.causeway.extensions.commandlog.applib.dom.ReplayState.FAILED) "
                  + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 1
    @NamedQuery(
            name  = Nq.FIND_MOST_RECENT_COMPLETED,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.startedAt   is not null "
                  + "   AND cl.completedAt is not null "
                  + " ORDER BY cl.timestamp DESC"), // programmatic LIMIT 1
    @NamedQuery(
            name  = Nq.FIND_BY_REPLAY_STATE,
            query = "SELECT cl "
                  + "  FROM CommandLogEntry cl "
                  + " WHERE cl.replayState = :replayState "
                  + " ORDER BY cl.timestamp ASC"), // programmatic LIMIT 10
})
@Named(CommandLogEntry.LOGICAL_TYPE_NAME)
@DomainObject(
        editing = Editing.DISABLED,
        entityChangePublishing = Publishing.DISABLED
)
@XmlJavaTypeAdapter(PersistentEntityAdapter.class)
@EntityListeners(CausewayEntityListener.class)
@NoArgsConstructor
public class CommandLogEntry extends org.apache.causeway.extensions.commandlog.applib.dom.CommandLogEntry {

    /**
     * Intended for use on secondary (replay) system.
     *
     * @param commandDto - obtained from the primary system as a representation of a command invocation
     * @param replayState - controls whether this is to be replayed
     * @param targetIndex - if the command represents a bulk action, then it is flattened out when replayed; this indicates which target to execute against.
     */
    public CommandLogEntry(
            final CommandDto commandDto,
            final org.apache.causeway.extensions.commandlog.applib.dom.ReplayState replayState,
            final int targetIndex) {
        super(commandDto, replayState, targetIndex);
    }

    @EmbeddedId
    private CommandLogEntryPK pk;

    @Transient
    @InteractionId
    @Override
    public UUID getInteractionId() {
        return pk != null ? pk.getInteractionId() : null;
    }
    @Transient
    @Override
    public void setInteractionId(UUID interactionId) {
        this.pk = new CommandLogEntryPK(interactionId);
    }

    @Column(nullable = Username.NULLABLE, length = Username.MAX_LENGTH)
    @Username
    @Getter @Setter
    private String username;

    @Column(nullable = Timestamp.NULLABLE)
    @Timestamp
    @Getter @Setter
    private java.sql.Timestamp timestamp;

    @Convert(converter = CausewayBookmarkConverter.class)
    @Column(nullable = Target.NULLABLE, length = Target.MAX_LENGTH)
    @Target
    @Getter @Setter
    private Bookmark target;

    @Column(nullable = ExecuteIn.NULLABLE, length = ExecuteIn.MAX_LENGTH)
    @Enumerated(EnumType.STRING)
    @ExecuteIn
    @Getter @Setter
    private org.apache.causeway.extensions.commandlog.applib.dom.ExecuteIn executeIn;

    @Convert(converter = JavaUtilUuidConverter.class)
    @Domain.Exclude
    @Column(nullable = Parent.NULLABLE, length = InteractionId.MAX_LENGTH)
    @Getter @Setter
    private UUID parentInteractionId;

    @Column(nullable = LogicalMemberIdentifier.NULLABLE, length = LogicalMemberIdentifier.MAX_LENGTH)
    @LogicalMemberIdentifier
    @Getter @Setter
    private String logicalMemberIdentifier;

    @Convert(converter = CausewayCommandDtoConverter.class)
    @Lob @Basic(fetch = FetchType.LAZY)
    @Column(nullable = CommandDtoAnnot.NULLABLE, columnDefinition = "CLOB")
    @CommandDtoAnnot
    @Getter @Setter
    private CommandDto commandDto;

    @Column(nullable = StartedAt.NULLABLE)
    @StartedAt
    @Getter @Setter
    private java.sql.Timestamp startedAt;

    @Column(nullable = CompletedAt.NULLABLE)
    @CompletedAt
    @Getter @Setter
    private java.sql.Timestamp completedAt;

    @Convert(converter = CausewayBookmarkConverter.class)
    @Column(nullable = Result.NULLABLE, length = Result.MAX_LENGTH)
    @Result
    @Getter @Setter
    private Bookmark result;

    @Lob @Basic(fetch = FetchType.LAZY)
    @Column(nullable = Exception.NULLABLE, columnDefinition = "CLOB")
    @Exception
    @Getter @Setter
    private String exception;

    @Column(nullable = ReplayState.NULLABLE, length = ReplayState.MAX_LENGTH)
    @Enumerated(EnumType.STRING)
    @ReplayState
    @Getter @Setter
    private org.apache.causeway.extensions.commandlog.applib.dom.ReplayState replayState;

    @Column(nullable = ReplayStateFailureReason.NULLABLE, length = ReplayStateFailureReason.MAX_LENGTH)
    @ReplayStateFailureReason
    @Getter @Setter
    private String replayStateFailureReason;

}
