using Workflow.Domain.Enums;
using Workflow.Domain.Exceptions;

namespace Workflow.Domain.StateMachine;

public class InstanceTransitionContext
{
    public bool HasActiveTokens { get; init; }
    public bool IsAdmin { get; init; }
    public bool IsInitiator { get; init; }
    public bool HasProcessedNodes { get; init; }
}

public class InstanceStateMachine
{
    public InstanceStatus Transition(InstanceStatus currentState, InstanceOperation operation, InstanceTransitionContext context)
    {
        return (currentState, operation) switch
        {
            (InstanceStatus.Running, InstanceOperation.Complete) => TransitionComplete(context),
            (InstanceStatus.Running, InstanceOperation.Suspend) => TransitionSuspend(context),
            (InstanceStatus.Suspended, InstanceOperation.Resume) => TransitionResume(context),
            (InstanceStatus.Running, InstanceOperation.Terminate) => TransitionTerminate(context),
            (InstanceStatus.Running, InstanceOperation.Withdraw) => TransitionWithdraw(context),
            _ => throw new InvalidStateTransitionException(
                $"Invalid instance state transition: cannot perform '{operation}' on instance in '{currentState}' state.")
        };
    }

    private static InstanceStatus TransitionComplete(InstanceTransitionContext context)
    {
        if (context.HasActiveTokens)
            throw new InvalidStateTransitionException(
                "Cannot complete instance: there are still active tokens.");

        return InstanceStatus.Completed;
    }

    private static InstanceStatus TransitionSuspend(InstanceTransitionContext context)
    {
        if (!context.IsAdmin && !context.IsInitiator)
            throw new InvalidStateTransitionException(
                "Only admin or initiator can suspend the instance.");

        return InstanceStatus.Suspended;
    }

    private static InstanceStatus TransitionResume(InstanceTransitionContext context)
    {
        if (!context.IsAdmin && !context.IsInitiator)
            throw new InvalidStateTransitionException(
                "Only admin or initiator can resume the instance.");

        return InstanceStatus.Running;
    }

    private static InstanceStatus TransitionTerminate(InstanceTransitionContext context)
    {
        if (!context.IsAdmin)
            throw new InvalidStateTransitionException(
                "Only admin can terminate the instance.");

        return InstanceStatus.Terminated;
    }

    private static InstanceStatus TransitionWithdraw(InstanceTransitionContext context)
    {
        if (!context.IsInitiator)
            throw new InvalidStateTransitionException(
                "Only the initiator can withdraw the instance.");

        if (context.HasProcessedNodes)
            throw new InvalidStateTransitionException(
                "Cannot withdraw instance: there are already processed nodes.");

        return InstanceStatus.Terminated;
    }
}
