Volver al índice

src/main/java/ar/com/companeros/decision/StrategicGate.java

package ar.com.companeros.decision;

import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.LongSupplier;

/** Permisos de Sol emitidos por estado confiable de facción, nunca por la frase del modelo. */
public final class StrategicGate {
    public enum Reason { MAIN_BASE, ABANDON_OR_RECOVER_BASE, FACTION_DEFENSE, DEFEAT_RECOVERY,
        COUNTERATTACK, FACTION_CONFLICT, UNRESOLVED_STRATEGY }
    public record Permit(UUID id, UUID factionId, UUID leaderId, Reason reason, long expiresAtUtc) { }
    private static final long COOLDOWN = 6 * 60 * 60 * 1000L;
    private static final long DAY = 24 * 60 * 60 * 1000L;
    private final boolean enabled;
    private final LongSupplier clock;
    private final Map<UUID, UUID> leaders = new HashMap<>();
    private final Map<UUID, Long> lastCalls = new HashMap<>();
    private final Map<UUID, Permit> issued = new HashMap<>();
    private final ArrayDeque<Long> calls = new ArrayDeque<>();

    public StrategicGate(boolean enabled) { this(enabled, System::currentTimeMillis); }
    public StrategicGate(boolean enabled, LongSupplier clock) { this.enabled = enabled; this.clock = clock; }

    // Estas operaciones pertenecen al servicio de facciones del servidor.
    public synchronized void recognizeLeader(UUID faction, UUID leader) {
        if (leaders.size() >= 256 && !leaders.containsKey(faction))
            throw new IllegalStateException("Registro estratégico fuera de límite");
        leaders.put(java.util.Objects.requireNonNull(faction), java.util.Objects.requireNonNull(leader));
        issued.values().removeIf(permit -> permit.factionId().equals(faction));
    }
    public synchronized void revokeLeader(UUID faction) {
        leaders.remove(faction); issued.values().removeIf(permit -> permit.factionId().equals(faction));
    }

    public synchronized Optional<Permit> authorize(UUID faction, UUID actor, Reason reason, boolean collectiveImpact) {
        long now = clock.getAsLong(); prune(now);
        if (!enabled || !collectiveImpact || reason == null || !actor.equals(leaders.get(faction))
                || calls.size() >= 4 || issued.size() >= 16 || coolingDown(faction, now)) return Optional.empty();
        Permit permit = new Permit(UUID.randomUUID(), faction, actor, reason, now + 60_000);
        issued.put(permit.id(), permit); return Optional.of(permit);
    }

    synchronized boolean consume(Permit permit, UUID actor) {
        long now = clock.getAsLong(); prune(now);
        if (!enabled || permit == null || !permit.equals(issued.get(permit.id()))
                || !actor.equals(permit.leaderId()) || !actor.equals(leaders.get(permit.factionId()))
                || calls.size() >= 4 || coolingDown(permit.factionId(), now)) return false;
        issued.remove(permit.id()); lastCalls.put(permit.factionId(), now); calls.addLast(now); return true;
    }

    private boolean coolingDown(UUID faction, long now) {
        Long last = lastCalls.get(faction); return last != null && now - last < COOLDOWN;
    }
    private void prune(long now) {
        issued.values().removeIf(permit -> permit.expiresAtUtc() <= now);
        while (!calls.isEmpty() && now - calls.peekFirst() >= DAY) calls.removeFirst();
        lastCalls.entrySet().removeIf(entry -> now - entry.getValue() >= DAY);
    }
}