package tech.easyflow.autoconfig.license;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
import tech.easyflow.common.util.HashUtil;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.time.Clock;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * EasyFlow license 校验器。
 */
public class EasyflowLicenseVerifier {

    static final String LICENSE_FILE_PREFIX = "ELIC1.";
    static final String PRODUCT_NAME = "EasyFlow";
    static final String SIGNATURE_ALGORITHM = "Ed25519";
    static final String FINGERPRINT_ALGORITHM = "sha256(machineId|productUuid|sortedMacs)";
    static final String DEFAULT_PUBLIC_KEY_LOCATION = "classpath:easy-license-public.pem";

    private static final Set<String> SUPPORTED_LICENSE_TYPES = Set.of("TRIAL", "STANDARD", "DEV");
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final ResourceLoader resourceLoader;
    private final MachineIdentityCollector machineIdentityCollector;
    private final Clock clock;
    private final String publicKeyLocation;

    /**
     * 构造校验器。
     *
     * @param resourceLoader Spring 资源加载器
     * @param machineIdentityCollector 机器信息采集器
     */
    public EasyflowLicenseVerifier(ResourceLoader resourceLoader, MachineIdentityCollector machineIdentityCollector) {
        this(resourceLoader, machineIdentityCollector, Clock.systemUTC(), DEFAULT_PUBLIC_KEY_LOCATION);
    }

    /**
     * 构造校验器。
     *
     * @param resourceLoader Spring 资源加载器
     * @param machineIdentityCollector 机器信息采集器
     * @param clock 时间时钟
     * @param publicKeyLocation 公钥位置
     */
    EasyflowLicenseVerifier(ResourceLoader resourceLoader,
                            MachineIdentityCollector machineIdentityCollector,
                            Clock clock,
                            String publicKeyLocation) {
        this.resourceLoader = resourceLoader;
        this.machineIdentityCollector = machineIdentityCollector;
        this.clock = clock;
        this.publicKeyLocation = publicKeyLocation;
    }

    /**
     * 校验指定位置的 license。
     *
     * @param location license 位置
     * @return 校验结果
     */
    public EasyflowLicenseVerificationResult verify(String location) {
        if (!StringUtils.hasText(location)) {
            throw EasyflowLicenseException.missing("未配置 easyflow.license.location", location);
        }

        try {
            ResolvedLicenseResource resolved = resolveLicenseResource(location);
            String content = readText(resolved.resource());
            return verifyContent(resolved.actualLocation(), content);
        } catch (EasyflowLicenseException ex) {
            throw ex;
        } catch (IOException e) {
            throw EasyflowLicenseException.missing("lic 文件读取失败", location, e);
        }
    }

    /**
     * 校验 license 文本内容。
     *
     * @param location license 位置
     * @param content license 文本
     * @return 校验结果
     */
    EasyflowLicenseVerificationResult verifyContent(String location, String content) {
        String normalizedContent = normalizeRequired(content, "licenseContent", location, null, null).trim();
        if (!normalizedContent.startsWith(LICENSE_FILE_PREFIX)) {
            throw EasyflowLicenseException.invalid("license 前缀非法", location, null, null);
        }

        JsonNode envelopeNode = decodeEnvelope(normalizedContent, location);
        String keyId = requireText(envelopeNode, "keyId", location, null, null);
        JsonNode payloadNode = requireNode(envelopeNode, "payload", location, null, keyId);
        String licenseId = requireText(payloadNode, "licenseId", location, null, keyId);

        validateEnvelope(envelopeNode, location, licenseId, keyId);
        validatePayload(payloadNode, location, licenseId, keyId);
        verifySignature(payloadNode, requireText(envelopeNode, "signature", location, licenseId, keyId), location, licenseId, keyId);
        verifyFingerprint(payloadNode, location, licenseId, keyId);

        String licenseType = requireText(payloadNode, "licenseType", location, licenseId, keyId);
        String expiresAt = payloadNode.path("expiresAt").isNull() ? null : payloadNode.path("expiresAt").asText(null);
        return new EasyflowLicenseVerificationResult(location, licenseId, keyId, licenseType, expiresAt);
    }

    /**
     * 构建机器指纹材料。
     *
     * @param machineId machineId
     * @param productUuid productUuid
     * @param macAddresses MAC 地址文本
     * @return 指纹材料
     */
    static String buildMachineFingerprintMaterial(String machineId, String productUuid, String macAddresses) {
        String normalizedMachineId = normalizeMachineId(machineId);
        String normalizedProductUuid = normalizeProductUuid(productUuid);
        List<String> normalizedMacs = normalizeMacAddresses(macAddresses);
        return "machineId=" + normalizedMachineId
                + "|productUuid=" + normalizedProductUuid
                + "|sortedMacs=" + String.join(",", normalizedMacs);
    }

    /**
     * 计算机器指纹。
     *
     * @param identity 机器信息
     * @return 机器指纹
     */
    static String computeMachineFingerprint(MachineIdentity identity) {
        return HashUtil.sha256(buildMachineFingerprintMaterial(identity.machineId(), identity.productUuid(), identity.macAddresses()));
    }

    /**
     * 归一化 MAC 地址文本列表。
     *
     * @param macAddresses MAC 地址文本
     * @return 归一化后的 MAC 地址列表
     */
    static List<String> normalizeMacAddresses(String macAddresses) {
        String normalized = normalizeRequired(macAddresses, "macAddresses", null, null, null);
        String[] parts = normalized.split("[,\\n]");
        LinkedHashSet<String> distinct = new LinkedHashSet<>();
        for (String part : parts) {
            if (StringUtils.hasText(part)) {
                distinct.add(normalizeMacAddress(part));
            }
        }
        if (distinct.isEmpty()) {
            throw new IllegalArgumentException("macAddresses 不能为空");
        }
        List<String> sorted = new ArrayList<>(distinct);
        sorted.sort(Comparator.naturalOrder());
        return sorted;
    }

    /**
     * 归一化单个 MAC 地址。
     *
     * @param macAddress MAC 地址
     * @return 归一化后的 MAC 地址
     */
    static String normalizeMacAddress(String macAddress) {
        String compact = normalizeRequired(macAddress, "macAddress", null, null, null)
                .toLowerCase(Locale.ROOT)
                .replaceAll("[^a-f0-9]", "");
        if (compact.length() != 12) {
            throw new IllegalArgumentException("MAC 地址非法: " + macAddress);
        }

        StringBuilder builder = new StringBuilder(17);
        for (int index = 0; index < compact.length(); index += 2) {
            if (index > 0) {
                builder.append(':');
            }
            builder.append(compact, index, index + 2);
        }
        return builder.toString();
    }

    /**
     * 归一化 machineId。
     *
     * @param machineId machineId
     * @return 归一化结果
     */
    static String normalizeMachineId(String machineId) {
        return normalizeRequired(machineId, "machineId", null, null, null).toLowerCase(Locale.ROOT);
    }

    /**
     * 归一化 productUuid。
     *
     * @param productUuid productUuid
     * @return 归一化结果
     */
    static String normalizeProductUuid(String productUuid) {
        return normalizeRequired(productUuid, "productUuid", null, null, null).toLowerCase(Locale.ROOT);
    }

    private JsonNode decodeEnvelope(String content, String location) {
        try {
            String encoded = content.substring(LICENSE_FILE_PREFIX.length());
            byte[] decodedBytes = Base64.getUrlDecoder().decode(encoded);
            return OBJECT_MAPPER.readTree(decodedBytes);
        } catch (IllegalArgumentException | IOException e) {
            throw EasyflowLicenseException.invalid("license 内容解码失败", location, null, null, e);
        }
    }

    private void validateEnvelope(JsonNode envelopeNode, String location, String licenseId, String keyId) {
        int formatVersion = envelopeNode.path("formatVersion").asInt(Integer.MIN_VALUE);
        if (formatVersion != 1) {
            throw EasyflowLicenseException.invalid("formatVersion 非法", location, licenseId, keyId);
        }
        String algorithm = requireText(envelopeNode, "alg", location, licenseId, keyId);
        if (!SIGNATURE_ALGORITHM.equals(algorithm)) {
            throw EasyflowLicenseException.invalid("签名算法非法", location, licenseId, keyId);
        }
        requireText(envelopeNode, "signature", location, licenseId, keyId);
    }

    private void validatePayload(JsonNode payloadNode, String location, String licenseId, String keyId) {
        String product = requireText(payloadNode, "product", location, licenseId, keyId);
        if (!PRODUCT_NAME.equals(product)) {
            throw EasyflowLicenseException.invalid("product 非法", location, licenseId, keyId);
        }

        String licenseType = requireText(payloadNode, "licenseType", location, licenseId, keyId);
        if (!SUPPORTED_LICENSE_TYPES.contains(licenseType)) {
            throw EasyflowLicenseException.invalid("licenseType 非法", location, licenseId, keyId);
        }

        String fingerprintAlgorithm = requireText(payloadNode, "fingerprintAlgorithm", location, licenseId, keyId);
        if (!FINGERPRINT_ALGORITHM.equals(fingerprintAlgorithm)) {
            throw EasyflowLicenseException.invalid("fingerprintAlgorithm 非法", location, licenseId, keyId);
        }

        requireText(payloadNode, "customerName", location, licenseId, keyId);
        requireText(payloadNode, "machineFingerprint", location, licenseId, keyId);

        parseInstant(requireText(payloadNode, "issuedAt", location, licenseId, keyId), "issuedAt", location, licenseId, keyId);

        JsonNode expiresAtNode = payloadNode.path("expiresAt");
        if ("DEV".equals(licenseType)) {
            if (!expiresAtNode.isMissingNode() && !expiresAtNode.isNull()) {
                throw EasyflowLicenseException.invalid("DEV license 不允许 expiresAt", location, licenseId, keyId);
            }
            return;
        }

        if (expiresAtNode.isMissingNode() || expiresAtNode.isNull()) {
            throw EasyflowLicenseException.invalid("非 DEV license 必须提供 expiresAt", location, licenseId, keyId);
        }

        Instant expiresAt = parseInstant(expiresAtNode.asText(), "expiresAt", location, licenseId, keyId);
        if (Instant.now(clock).isAfter(expiresAt)) {
            throw EasyflowLicenseException.invalid("license 已过期", location, licenseId, keyId);
        }
    }

    private void verifySignature(JsonNode payloadNode, String signatureValue, String location, String licenseId, String keyId) {
        try {
            PublicKey publicKey = loadPublicKey();
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(LicenseCanonicalJsonSerializer.serialize(payloadNode).getBytes(StandardCharsets.UTF_8));
            boolean valid = signature.verify(Base64.getDecoder().decode(signatureValue));
            if (!valid) {
                throw EasyflowLicenseException.invalid("license 签名校验失败", location, licenseId, keyId);
            }
        } catch (EasyflowLicenseException ex) {
            throw ex;
        } catch (GeneralSecurityException | IOException | IllegalArgumentException e) {
            throw EasyflowLicenseException.invalid("license 签名校验失败", location, licenseId, keyId, e);
        }
    }

    private void verifyFingerprint(JsonNode payloadNode, String location, String licenseId, String keyId) {
        String expectedFingerprint = requireText(payloadNode, "machineFingerprint", location, licenseId, keyId);
        MachineIdentity identity;
        try {
            identity = machineIdentityCollector.collect();
        } catch (Exception e) {
            throw EasyflowLicenseException.invalid("机器参数采集失败: " + e.getMessage(), location, licenseId, keyId, e);
        }

        String actualFingerprint;
        try {
            actualFingerprint = computeMachineFingerprint(identity);
        } catch (IllegalArgumentException e) {
            throw EasyflowLicenseException.invalid("机器指纹计算失败: " + e.getMessage(), location, licenseId, keyId, e);
        }

        if (!expectedFingerprint.equals(actualFingerprint)) {
            throw EasyflowLicenseException.invalid("license 机器指纹不匹配", location, licenseId, keyId);
        }
    }

    private PublicKey loadPublicKey() throws GeneralSecurityException, IOException {
        Resource publicKeyResource = resourceLoader.getResource(publicKeyLocation);
        if (!publicKeyResource.exists() || !publicKeyResource.isReadable()) {
            throw new IOException("公钥资源不存在或不可读: " + publicKeyLocation);
        }
        String pem = readText(publicKeyResource);
        String normalizedPem = pem
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s+", "");
        byte[] keyBytes = Base64.getDecoder().decode(normalizedPem);
        return KeyFactory.getInstance(SIGNATURE_ALGORITHM).generatePublic(new X509EncodedKeySpec(keyBytes));
    }

    private String readText(Resource resource) throws IOException {
        try (InputStream inputStream = resource.getInputStream()) {
            return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).trim();
        }
    }

    private ResolvedLicenseResource resolveLicenseResource(String location) {
        String normalizedLocation = location.trim();
        if (normalizedLocation.startsWith("file:")) {
            return resolveFileLicenseResource(normalizedLocation, normalizedLocation.substring("file:".length()));
        }

        Resource resource = resourceLoader.getResource(normalizedLocation);
        if (!resource.exists() || !resource.isReadable()) {
            throw EasyflowLicenseException.missing("lic 文件不存在", normalizedLocation);
        }
        return new ResolvedLicenseResource(resource, normalizedLocation);
    }

    private ResolvedLicenseResource resolveFileLicenseResource(String originalLocation, String rawPath) {
        Path candidatePath = Paths.get(rawPath).toAbsolutePath().normalize();

        if (Files.isDirectory(candidatePath)) {
            return resolveSingleLicFile(candidatePath, originalLocation);
        }

        if (Files.exists(candidatePath) && Files.isRegularFile(candidatePath) && Files.isReadable(candidatePath)) {
            return new ResolvedLicenseResource(resourceLoader.getResource(candidatePath.toUri().toString()), candidatePath.toUri().toString());
        }

        if (candidatePath.getFileName() != null
                && candidatePath.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(".lic")) {
            Path parent = candidatePath.getParent();
            if (parent != null && Files.isDirectory(parent)) {
                return resolveSingleLicFile(parent, originalLocation);
            }
        }

        throw EasyflowLicenseException.missing("lic 文件不存在", originalLocation);
    }

    private ResolvedLicenseResource resolveSingleLicFile(Path directory, String originalLocation) {
        try (Stream<Path> stream = Files.list(directory)) {
            List<Path> licFiles = stream
                    .filter(Files::isRegularFile)
                    .filter(Files::isReadable)
                    .filter(path -> path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(".lic"))
                    .sorted()
                    .collect(Collectors.toList());

            if (licFiles.isEmpty()) {
                throw EasyflowLicenseException.missing("lic 文件不存在", originalLocation);
            }
            if (licFiles.size() > 1) {
                throw EasyflowLicenseException.missing("检测到多个 .lic 文件，无法确定应使用哪个文件", originalLocation);
            }

            Path licFile = licFiles.get(0);
            return new ResolvedLicenseResource(resourceLoader.getResource(licFile.toUri().toString()), licFile.toUri().toString());
        } catch (IOException e) {
            throw EasyflowLicenseException.missing("lic 文件读取失败", originalLocation, e);
        }
    }

    private static String requireText(JsonNode node, String field, String location, String licenseId, String keyId) {
        JsonNode value = node.path(field);
        if (value.isMissingNode() || value.isNull()) {
            throw EasyflowLicenseException.invalid(field + " 缺失", location, licenseId, keyId);
        }
        return normalizeRequired(value.asText(), field, location, licenseId, keyId);
    }

    private static JsonNode requireNode(JsonNode node, String field, String location, String licenseId, String keyId) {
        JsonNode value = node.path(field);
        if (value.isMissingNode() || value.isNull()) {
            throw EasyflowLicenseException.invalid(field + " 缺失", location, licenseId, keyId);
        }
        return value;
    }

    private static Instant parseInstant(String value, String field, String location, String licenseId, String keyId) {
        try {
            return Instant.parse(value);
        } catch (Exception e) {
            throw EasyflowLicenseException.invalid(field + " 时间格式非法", location, licenseId, keyId, e);
        }
    }

    private static String normalizeRequired(String value, String field, String location, String licenseId, String keyId) {
        String normalized = value == null ? "" : value.trim();
        if (!StringUtils.hasText(normalized)) {
            if (location != null || licenseId != null || keyId != null) {
                throw EasyflowLicenseException.invalid(field + " 不能为空", location, licenseId, keyId);
            }
            throw new IllegalArgumentException(field + " 不能为空");
        }
        return normalized;
    }

    /**
     * 已解析的 license 资源。
     *
     * @param resource Spring 资源
     * @param actualLocation 实际使用的位置
     */
    private record ResolvedLicenseResource(Resource resource, String actualLocation) {
    }
}
