package com.cameleer.server.app.config;

import com.cameleer.server.app.license.LicenseRepository;
import com.cameleer.server.app.license.LicenseService;
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 jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Base64;
import java.util.Optional;

/**
 * License bean topology (4 beans, in dependency order):
 *
 * <ol>
 *   <li>{@link LicenseGate} — always present, mutated by {@link LicenseService}.</li>
 *   <li>{@link LicenseValidator} — always present. When no public key is configured, returns an
 *       always-failing override so any loaded token routes through {@code install()} and is
 *       audited as INVALID rather than silently ignored.</li>
 *   <li>{@link LicenseService} — single mediation point for install / replace / revalidate;
 *       audits + persists + publishes {@code LicenseChangedEvent}.</li>
 *   <li>{@link LicenseBootLoader} — {@code @PostConstruct} drives {@code loadInitial} after the
 *       Spring context is ready. Resolution order: env var &gt; license file &gt; persisted DB row.</li>
 * </ol>
 */
@Configuration
public class LicenseBeanConfig {

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

    @Value("${cameleer.server.tenant.id:default}")
    private String tenantId;

    @Value("${cameleer.server.license.token:}")
    private String licenseToken;

    @Value("${cameleer.server.license.file:}")
    private String licenseFile;

    @Value("${cameleer.server.license.publickey:}")
    private String licensePublicKey;

    @Bean
    public LicenseGate licenseGate() {
        return new LicenseGate();
    }

    @Bean
    public LicenseValidator licenseValidator() {
        if (licensePublicKey == null || licensePublicKey.isBlank()) {
            log.warn("CAMELEER_SERVER_LICENSE_PUBLICKEY not set — all licenses will be rejected as INVALID");
            // Generate a throwaway, structurally-valid Ed25519 keypair just to satisfy the
            // parent constructor's X.509 SubjectPublicKeyInfo decode + Ed25519 point validation.
            // The overridden validate(...) always throws, so the dummy key is never used to
            // verify anything — it only exists so the bean is constructable in misconfigured
            // installs and any token that is loaded routes to INVALID via install()'s catch.
            try {
                KeyPair throwaway = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
                String dummyPub = Base64.getEncoder().encodeToString(throwaway.getPublic().getEncoded());
                return new LicenseValidator(dummyPub, tenantId) {
                    @Override
                    public LicenseInfo validate(String token) {
                        throw new IllegalStateException("license public key not configured");
                    }
                };
            } catch (Exception e) {
                throw new IllegalStateException("Failed to construct fallback license validator", e);
            }
        }
        return new LicenseValidator(licensePublicKey, tenantId);
    }

    @Bean
    public LicenseService licenseService(LicenseRepository repo,
                                         LicenseGate gate,
                                         LicenseValidator validator,
                                         AuditService audit,
                                         ApplicationEventPublisher events) {
        return new LicenseService(tenantId, repo, gate, validator, audit, events);
    }

    @Bean
    public LicenseBootLoader licenseBootLoader(LicenseService svc) {
        return new LicenseBootLoader(svc, licenseToken, licenseFile);
    }

    /**
     * {@code @PostConstruct} bridge that converts env-var/file values into the
     * {@code Optional<String>} pair {@link LicenseService#loadInitial} expects, so
     * env-var, file, and DB paths share the same audit + event-publish code path.
     */
    public static class LicenseBootLoader {
        private final LicenseService svc;
        private final String envToken;
        private final String filePath;

        public LicenseBootLoader(LicenseService svc, String envToken, String filePath) {
            this.svc = svc;
            this.envToken = envToken;
            this.filePath = filePath;
        }

        @PostConstruct
        public void load() {
            Optional<String> env = (envToken != null && !envToken.isBlank())
                    ? Optional.of(envToken) : Optional.empty();
            Optional<String> file = Optional.empty();
            if (filePath != null && !filePath.isBlank()) {
                try {
                    file = Optional.of(Files.readString(Path.of(filePath)).trim());
                } catch (Exception e) {
                    log.warn("Failed to read license file {}: {}", filePath, e.getMessage());
                }
            }
            svc.loadInitial(env, file);
        }
    }
}
