package click.kamil.service;

import click.kamil.domain.IEvent;
import click.kamil.domain.IState;
import click.kamil.domain.OrderEvent;
import click.kamil.domain.OrderState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import reactor.core.publisher.Mono;

@Service
public class StateMachineServiceImpl implements StateMachineService {

    @Autowired
    private StateMachine<OrderState, OrderEvent> stateMachine;

    @Override
    public <S extends IState, T extends IEvent> void sendMessage(T event) {
        if (event instanceof OrderEvent) {
            stateMachine.sendEvent(Mono.just(MessageBuilder.withPayload((OrderEvent) event).build())).subscribe();
        }
    }

    @Override
    @Transactional
    public <S extends IState, T extends IEvent, A extends OutboxAction<T>> void sendMessageWithOutbox(T event, A action) {
        sendMessage(event);

        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    action.onCommit(event);
                }
            });
        } else {
            action.onCommit(event);
        }
    }

    @Override
    public <T extends OrderEvent> void sendMessageWithProvider(java.util.function.Supplier<T> eventProvider) {
        T event = eventProvider.get();
        if (event != null) {
            stateMachine.sendEvent(Mono.just(MessageBuilder.withPayload((OrderEvent) event).build())).subscribe();
        }
    }

    @Override
    @SafeVarargs
    public final <T extends IEvent> void sendEventsVarargs(T... events) {
        for (T event : events) {
            sendMessage(event);
        }
    }

    @Override
    public <T extends IEvent> void executeFunctionalTransition(java.util.function.Supplier<T> eventSupplier, 
                                                               java.util.function.Function<T, Void> transitionAction) {
        // Resolve the event from the getter / supplier function
        T event = eventSupplier.get();
        // Apply the transition via the provided function / method reference
        transitionAction.apply(event);
    }

    @Override
    public <T extends OrderEvent, P> void sendCustomMessage(click.kamil.domain.CustomStateMachineMessage<T, P> customMessage) {
        // Because CustomStateMachineMessage extends GenericMessage<T> and implements Message<T>,
        // we can pass it directly to the state machine as long as T resolves to OrderEvent!
        
        // We cast it to Message<OrderEvent> to satisfy Mono's strict type requirements
        org.springframework.messaging.Message<OrderEvent> msg = (org.springframework.messaging.Message<OrderEvent>) customMessage;
        stateMachine.sendEvent(Mono.just(msg)).subscribe();
    }

    @Override
    public <T extends IEvent & java.io.Serializable & Cloneable> T processQuirkyEvent(T event) {
        // Quirky pattern 3: Multiple bounds, returning the same generic type
        sendMessage(event);
        return event; // returns something that is guaranteed to be IEvent, Serializable, AND Cloneable
    }
}
