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.LicenseInfo;
import com.cameleer.server.core.license.LicenseValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;

import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

/**
 * Single mediation point for license token install / replace / revalidate.
 *
 * <p>Audits under {@link AuditCategory#LICENSE}, persists to PostgreSQL via
 * {@link LicenseRepository}, mutates the in-memory {@link LicenseGate}, and publishes a
 * {@link LicenseChangedEvent} so downstream listeners (retention policy, license metrics,
 * etc.) react uniformly to every state change.</p>
 */
public class LicenseService {

    private static final Logger log = LoggerFactory.getLogger(LicenseService.class);

    private final String tenantId;
    private final LicenseRepository repo;
    private final LicenseGate gate;
    private final LicenseValidator validator;
    private final AuditService audit;
    private final ApplicationEventPublisher events;

    public LicenseService(String tenantId, LicenseRepository repo, LicenseGate gate,
                          LicenseValidator validator, AuditService audit,
                          ApplicationEventPublisher events) {
        this.tenantId = tenantId;
        this.repo = repo;
        this.gate = gate;
        this.validator = validator;
        this.audit = audit;
        this.events = events;
    }

    /** Install a token from any source (env, file, api, db). */
    public LicenseInfo install(String token, String installedBy, String source) {
        LicenseInfo info;
        try {
            info = validator.validate(token);
        } catch (Exception e) {
            String reason = e.getMessage();
            gate.markInvalid(reason);
            Map<String, Object> detail = new LinkedHashMap<>();
            detail.put("reason", reason);
            detail.put("source", source);
            audit.log(installedBy, "reject_license", AuditCategory.LICENSE,
                    tenantId, detail, AuditResult.FAILURE, null);
            events.publishEvent(new LicenseChangedEvent(gate.getState(), gate.getCurrent()));
            throw e instanceof RuntimeException re ? re : new IllegalArgumentException(e);
        }

        Optional<LicenseRecord> existing = repo.findByTenantId(tenantId);
        Instant now = Instant.now();
        repo.upsert(new LicenseRecord(
                tenantId, token, info.licenseId(),
                now, installedBy, info.expiresAt(), now));
        gate.load(info);

        Map<String, Object> detail = new LinkedHashMap<>();
        detail.put("licenseId", info.licenseId().toString());
        detail.put("expiresAt", info.expiresAt().toString());
        detail.put("installedBy", installedBy);
        detail.put("source", source);
        if (existing.isPresent()) {
            detail.put("previousLicenseId", existing.get().licenseId().toString());
            audit.log(installedBy, "replace_license", AuditCategory.LICENSE,
                    info.licenseId().toString(), detail, AuditResult.SUCCESS, null);
        } else {
            audit.log(installedBy, "install_license", AuditCategory.LICENSE,
                    info.licenseId().toString(), detail, AuditResult.SUCCESS, null);
        }

        events.publishEvent(new LicenseChangedEvent(gate.getState(), info));
        return info;
    }

    /** Boot-time load: prefer env/file overrides; falls back to DB; ABSENT if none. */
    public void loadInitial(Optional<String> envToken, Optional<String> fileToken) {
        if (envToken.isPresent()) {
            try { install(envToken.get(), "system", "env"); return; }
            catch (Exception e) { log.error("env-var license rejected: {}", e.getMessage()); }
        }
        if (fileToken.isPresent()) {
            try { install(fileToken.get(), "system", "file"); return; }
            catch (Exception e) { log.error("file license rejected: {}", e.getMessage()); }
        }
        Optional<LicenseRecord> persisted = repo.findByTenantId(tenantId);
        if (persisted.isPresent()) {
            try { install(persisted.get().token(), persisted.get().installedBy(), "db"); }
            catch (Exception e) { log.error("DB license rejected: {}", e.getMessage()); }
        } else {
            log.info("No license configured - running in default tier");
            events.publishEvent(new LicenseChangedEvent(gate.getState(), null));
        }
    }

    /** Re-run validation against the persisted token (daily job). */
    public void revalidate() {
        Optional<LicenseRecord> persisted = repo.findByTenantId(tenantId);
        if (persisted.isEmpty()) return;
        try {
            LicenseInfo info = validator.validate(persisted.get().token());
            repo.touchValidated(tenantId, Instant.now());
            gate.load(info);
            events.publishEvent(new LicenseChangedEvent(gate.getState(), info));
        } catch (Exception e) {
            String reason = e.getMessage();
            gate.markInvalid(reason);
            Map<String, Object> detail = new LinkedHashMap<>();
            detail.put("licenseId", persisted.get().licenseId().toString());
            detail.put("reason", reason);
            audit.log("system", "revalidate_license", AuditCategory.LICENSE,
                    persisted.get().licenseId().toString(), detail, AuditResult.FAILURE, null);
            events.publishEvent(new LicenseChangedEvent(gate.getState(), null));
            log.error("Revalidation failed: {}", reason);
        }
    }

    public String getTenantId() { return tenantId; }
}
