package com.easyagents.federation.sql.source;

import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 可持久化、可跨节点传递的数据源定义。
 *
 * @param sourceId 逻辑数据源标识
 * @param revision 单调递增版本
 * @param adapterId Adapter 标识
 * @param schemas Schema 定义
 * @param adapterOptions 不含凭据的 Adapter 选项
 */
public record FederationSourceDefinition(
    SourceId sourceId,
    long revision,
    String adapterId,
    List<FederationSchemaDefinition> schemas,
    Map<String, String> adapterOptions
) implements Serializable {

    /**
     * 校验并防御性复制数据源定义。
     */
    public FederationSourceDefinition {
        if (sourceId == null) {
            throw new IllegalArgumentException("sourceId must not be null");
        }
        if (revision < 0) {
            throw new IllegalArgumentException("revision must not be negative");
        }
        if (adapterId == null || adapterId.isBlank()) {
            throw new IllegalArgumentException("adapterId must not be blank");
        }
        schemas = List.copyOf(schemas == null ? List.of() : schemas);
        if (schemas.isEmpty()) {
            throw new IllegalArgumentException("schemas must not be empty");
        }
        Set<String> logicalNames = new HashSet<>();
        for (FederationSchemaDefinition schema : schemas) {
            if (schema == null || !logicalNames.add(schema.logicalName())) {
                throw new IllegalArgumentException("schema definitions must be non-null and uniquely named");
            }
        }
        adapterOptions = Map.copyOf(adapterOptions == null ? Map.of() : adapterOptions);
    }

    /**
     * 计算不包含凭据的稳定定义校验和。
     *
     * @return SHA-256 十六进制校验和
     */
    public String checksum() {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            updateField(digest, sourceId.value());
            updateField(digest, Long.toString(revision));
            updateField(digest, adapterId);
            List<FederationSchemaDefinition> orderedSchemas = new ArrayList<>(schemas);
            orderedSchemas.sort(Comparator.comparing(FederationSchemaDefinition::logicalName));
            updateCount(digest, orderedSchemas.size());
            for (FederationSchemaDefinition schema : orderedSchemas) {
                updateField(digest, schema.getClass().getName());
                updateField(digest, schema.logicalName());
                List<String> checksumFields = schema.checksumFields();
                updateCount(digest, checksumFields.size());
                for (String field : checksumFields) {
                    updateNullableField(digest, field);
                }
            }
            Map<String, String> orderedOptions = new TreeMap<>(adapterOptions);
            updateCount(digest, orderedOptions.size());
            orderedOptions.forEach((key, value) -> {
                updateField(digest, key);
                updateField(digest, value);
            });
            return HexFormat.of().formatHex(digest.digest());
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("SHA-256 is not available", exception);
        }
    }

    private static void updateField(MessageDigest digest, String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
        updateCount(digest, bytes.length);
        digest.update(bytes);
    }

    private static void updateCount(MessageDigest digest, int value) {
        digest.update(ByteBuffer.allocate(Integer.BYTES).putInt(value).array());
    }

    private static void updateNullableField(MessageDigest digest, String value) {
        if (value == null) {
            updateCount(digest, -1);
            return;
        }
        updateField(digest, value);
    }
}
