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 leaders = new HashMap<>(); private final Map lastCalls = new HashMap<>(); private final Map issued = new HashMap<>(); private final ArrayDeque 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 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); } }