package com.cameleer.server.core.license;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

public class LicenseValidator {

    private static final Logger log = LoggerFactory.getLogger(LicenseValidator.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    private final PublicKey publicKey;
    private final String expectedTenantId;

    public LicenseValidator(String publicKeyBase64, String expectedTenantId) {
        Objects.requireNonNull(expectedTenantId, "expectedTenantId is required");
        if (expectedTenantId.isBlank()) {
            throw new IllegalArgumentException("expectedTenantId must not be blank");
        }
        try {
            byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64);
            KeyFactory kf = KeyFactory.getInstance("Ed25519");
            this.publicKey = kf.generatePublic(new X509EncodedKeySpec(keyBytes));
        } catch (Exception e) {
            throw new IllegalStateException("Failed to load license public key", e);
        }
        this.expectedTenantId = expectedTenantId;
    }

    public LicenseInfo validate(String token) {
        String[] parts = token.split("\\.", 2);
        if (parts.length != 2) {
            throw new IllegalArgumentException("Invalid license token format: expected payload.signature");
        }

        byte[] payloadBytes = Base64.getDecoder().decode(parts[0]);
        byte[] signatureBytes = Base64.getDecoder().decode(parts[1]);

        try {
            Signature verifier = Signature.getInstance("Ed25519");
            verifier.initVerify(publicKey);
            verifier.update(payloadBytes);
            if (!verifier.verify(signatureBytes)) {
                throw new SecurityException("License signature verification failed");
            }
        } catch (SecurityException e) {
            throw e;
        } catch (Exception e) {
            throw new SecurityException("License signature verification failed", e);
        }

        try {
            JsonNode root = objectMapper.readTree(payloadBytes);

            String licenseIdStr = textOrThrow(root, "licenseId");
            UUID licenseId;
            try {
                licenseId = UUID.fromString(licenseIdStr);
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("licenseId is not a valid UUID: " + licenseIdStr);
            }

            String tenantId = textOrThrow(root, "tenantId");
            if (!tenantId.equals(expectedTenantId)) {
                throw new IllegalArgumentException(
                        "License tenantId '" + tenantId + "' does not match server tenant '" + expectedTenantId + "'");
            }

            String label = root.has("label") ? root.get("label").asText() : null;

            Map<String, Integer> limits = new HashMap<>();
            if (root.has("limits")) {
                root.get("limits").fields().forEachRemaining(entry ->
                        limits.put(entry.getKey(), entry.getValue().asInt()));
            }

            Instant issuedAt = root.has("iat") ? Instant.ofEpochSecond(root.get("iat").asLong()) : Instant.now();
            if (!root.has("exp")) {
                throw new IllegalArgumentException("exp is required");
            }
            Instant expiresAt = Instant.ofEpochSecond(root.get("exp").asLong());
            int gracePeriodDays = root.has("gracePeriodDays") ? root.get("gracePeriodDays").asInt() : 0;

            LicenseInfo info = new LicenseInfo(licenseId, tenantId, label, limits, issuedAt, expiresAt, gracePeriodDays);

            if (info.isExpired()) {
                throw new IllegalArgumentException("License expired at " + expiresAt
                        + " (grace period " + gracePeriodDays + " days)");
            }

            return info;
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException("Failed to parse license payload", e);
        }
    }

    private static String textOrThrow(JsonNode root, String field) {
        if (!root.has(field) || root.get(field).asText().isBlank()) {
            throw new IllegalArgumentException(field + " is required");
        }
        return root.get(field).asText();
    }
}
