package com.cameleer.server.app.license;

import com.cameleer.server.core.admin.AuditCategory;
import com.cameleer.server.core.admin.AuditResult;
import com.cameleer.server.core.admin.AuditService;
import com.cameleer.server.core.license.LicenseGate;
import com.cameleer.server.core.license.LicenseLimits;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Single entry point for license cap enforcement (spec §4).
 *
 * <p>Consults {@link LicenseGate#getEffectiveLimits()} (license-overrides UNION default tier when
 * ACTIVE/GRACE; defaults-only otherwise) and rejects calls whose projected usage would exceed the
 * cap. Rejections increment a per-limit Micrometer counter and, when an {@link AuditService} is
 * wired, emit an {@link AuditCategory#LICENSE} {@code cap_exceeded} audit row.</p>
 *
 * <p>Unknown limit keys are treated as programmer errors and surface as
 * {@link IllegalArgumentException} (propagated from {@link LicenseLimits#get(String)}), not
 * {@link LicenseCapExceededException}.</p>
 */
@Component
public class LicenseEnforcer {

    private static final Logger log = LoggerFactory.getLogger(LicenseEnforcer.class);
    private static final String COUNTER_NAME = "cameleer_license_cap_rejections_total";

    private final LicenseGate gate;
    private final MeterRegistry meters;
    private final AuditService audit;
    private final ConcurrentMap<String, Counter> rejectionCounters = new ConcurrentHashMap<>();

    @Autowired
    public LicenseEnforcer(LicenseGate gate, MeterRegistry meters, AuditService audit) {
        this.gate = gate;
        this.meters = meters;
        this.audit = audit;
    }

    /** Test-only ctor with no metrics or audit. */
    public LicenseEnforcer(LicenseGate gate) {
        this(gate, new SimpleMeterRegistry(), null);
    }

    public void assertWithinCap(String limitKey, long currentUsage, long requestedDelta) {
        LicenseLimits effective = gate.getEffectiveLimits();
        int cap = effective.get(limitKey); // throws IllegalArgumentException if unknown key
        long projected = currentUsage + requestedDelta;
        if (projected > cap) {
            rejectionCounters.computeIfAbsent(limitKey, k -> Counter.builder(COUNTER_NAME)
                    .tag("limit", k).register(meters)).increment();
            if (audit != null) {
                try {
                    Map<String, Object> detail = new LinkedHashMap<>();
                    detail.put("limit", limitKey);
                    detail.put("current", currentUsage);
                    detail.put("requested", requestedDelta);
                    detail.put("cap", cap);
                    detail.put("state", gate.getState().name());
                    audit.log("system", "cap_exceeded", AuditCategory.LICENSE, limitKey, detail, AuditResult.FAILURE, null);
                } catch (RuntimeException e) {
                    // Audit storage degraded; log and continue so the cap rejection still surfaces as 403.
                    log.warn("Failed to write cap_exceeded audit row for limit={}: {}", limitKey, e.toString());
                }
            }
            throw new LicenseCapExceededException(limitKey, projected, cap);
        }
    }
}
