package com.cameleer.server.app.alerting.notify;

import com.cameleer.server.app.alerting.dto.UnreadCountResponse;
import com.cameleer.server.core.alerting.AlertInstance;
import com.cameleer.server.core.alerting.AlertInstanceRepository;
import com.cameleer.server.core.alerting.AlertSeverity;
import com.cameleer.server.core.alerting.AlertState;
import com.cameleer.server.core.rbac.RbacService;
import org.springframework.stereotype.Component;

import java.time.Clock;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Server-side query helper for the in-app alert inbox.
 * <p>
 * {@link #listInbox} returns alerts the user is allowed to see (targeted directly or via group/role).
 * {@link #countUnread} is memoized per {@code (envId, userId)} for 5 seconds to avoid hammering
 * the database on every page render. The memo caches the full per-severity breakdown so
 * the UI can branch bell colour on the highest unread severity without a second call.
 */
@Component
public class InAppInboxQuery {

    private static final long MEMO_TTL_MS = 5_000L;

    private final AlertInstanceRepository instanceRepo;
    private final RbacService rbacService;
    private final Clock clock;

    /** Cache key for the unread count memo. */
    private record Key(UUID envId, String userId) {}

    /** Cache entry: cached response + expiry timestamp. */
    private record Entry(UnreadCountResponse response, Instant expiresAt) {}

    private final ConcurrentHashMap<Key, Entry> memo = new ConcurrentHashMap<>();

    public InAppInboxQuery(AlertInstanceRepository instanceRepo,
                           RbacService rbacService,
                           Clock alertingClock) {
        this.instanceRepo = instanceRepo;
        this.rbacService  = rbacService;
        this.clock        = alertingClock;
    }

    /**
     * Full filtered variant: optional {@code states}, {@code severities}, {@code acked},
     * and {@code read} narrow the result set. {@code null} or empty lists mean
     * "no filter on that dimension". {@code acked}/{@code read} are tri-state:
     * {@code null} = no filter, {@code TRUE} = only acked/read, {@code FALSE} = only unacked/unread.
     */
    public List<AlertInstance> listInbox(UUID envId,
                                         String userId,
                                         List<AlertState> states,
                                         List<AlertSeverity> severities,
                                         Boolean acked,
                                         Boolean read,
                                         int limit) {
        List<String> groupIds   = resolveGroupIds(userId);
        List<String> roleNames  = resolveRoleNames(userId);
        return instanceRepo.listForInbox(envId, groupIds, userId, roleNames,
                states, severities, acked, read, limit);
    }

    /**
     * Returns the unread (un-acked) alert count for the user, broken down by severity.
     * <p>
     * Memoized for 5 seconds per {@code (envId, userId)}.
     */
    public UnreadCountResponse countUnread(UUID envId, String userId) {
        Key key = new Key(envId, userId);
        Instant now = Instant.now(clock);
        Entry cached = memo.get(key);
        if (cached != null && now.isBefore(cached.expiresAt())) {
            return cached.response();
        }
        List<String> groupIds  = resolveGroupIds(userId);
        List<String> roleNames = resolveRoleNames(userId);
        Map<AlertSeverity, Long> bySeverity = instanceRepo.countUnreadBySeverity(envId, userId, groupIds, roleNames);
        UnreadCountResponse response = UnreadCountResponse.from(bySeverity);
        memo.put(key, new Entry(response, now.plusMillis(MEMO_TTL_MS)));
        return response;
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private List<String> resolveGroupIds(String userId) {
        return rbacService.getEffectiveGroupsForUser(userId)
                .stream()
                .map(g -> g.id().toString())
                .toList();
    }

    private List<String> resolveRoleNames(String userId) {
        return rbacService.getEffectiveRolesForUser(userId)
                .stream()
                .map(r -> r.name())
                .toList();
    }
}
