// ============================================================
// validators.ts — env.yaml 配置类型定义与运行时校验
// ============================================================
// 职责：
//   1. 定义 env.yaml 的 TypeScript 接口
//   2. 提供运行时校验函数（零依赖，手动实现）
//   3. 为 config.ts 提供类型安全的配置读取
//
// 使用方式：
//   import { validateConfig, type EnvConfig } from "./db/validators";
//   const raw = validateConfig(parsedYaml);
// ============================================================

/** env.yaml 顶层结构 */
export interface EnvConfig {
    db: DbConfig;
    redis: RedisConfig;
    exchange: ExchangeConfig;
    logging: LoggingConfig;
}

export interface DbConfig {
    host: string;
    port: number;
    name: string;
    user: string;
    password: string;
}

export interface RedisConfig {
    url: string;
    publish_enabled: boolean;
}

/** 交易所 API 密钥配置（按交易所 ID 索引） */
export interface ExchangeConfig {
    /** 现货市场 API Key */
    binance: ExchangeApiKeys;
    /** USDT-M 永续合约 API Key */
    binance_futures: ExchangeApiKeys;
    // 未来扩展：okx、bybit 等
    [exchangeId: string]: ExchangeApiKeys | undefined;
}

export interface ExchangeApiKeys {
    api_key: string;
    api_secret: string;
}

export interface LoggingConfig {
    level: "trace" | "debug" | "info" | "warn" | "error" | "fatal";
    node_env: "development" | "production" | "test";
}

// ============================================================
// 运行时校验（零依赖）
// ============================================================

const VALID_LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"] as const;
const VALID_NODE_ENVS = ["development", "production", "test"] as const;

/**
 * 校验并返回类型安全的配置对象。
 * 校验失败时抛出明确错误信息，遵循 fail-fast 原则。
 */
export function validateConfig(raw: unknown): EnvConfig {
    if (typeof raw !== "object" || raw === null) {
        throw new Error(`[config] env.yaml 顶层必须为 object，实际: ${typeof raw}`);
    }

    const obj = raw as Record<string, unknown>;

    // --- db ---
    const db = obj["db"];
    if (typeof db !== "object" || db === null) {
        throw new Error("[config] env.yaml 缺少 db 配置段");
    }
    const dbObj = db as Record<string, unknown>;

    const dbHost = assertString(dbObj["host"], "db.host");
    const dbPort = assertPort(dbObj["port"], "db.port");
    const dbName = assertString(dbObj["name"], "db.name");
    const dbUser = assertString(dbObj["user"], "db.user");
    const dbPassword = assertString(dbObj["password"], "db.password");

    // --- redis ---
    const redis = obj["redis"];
    if (typeof redis !== "object" || redis === null) {
        throw new Error("[config] env.yaml 缺少 redis 配置段");
    }
    const redisObj = redis as Record<string, unknown>;

    const redisUrl = assertString(redisObj["url"], "redis.url");
    const redisPublishEnabled = assertBoolean(redisObj["publish_enabled"], "redis.publish_enabled");

    // --- exchange ---
    const exchange = obj["exchange"];
    if (typeof exchange !== "object" || exchange === null) {
        throw new Error("[config] env.yaml 缺少 exchange 配置段");
    }
    const exObj = exchange as Record<string, unknown>;

    const binance = exObj["binance"];
    if (typeof binance !== "object" || binance === null) {
        throw new Error("[config] env.yaml exchange 缺少 binance 配置");
    }
    const binanceObj = binance as Record<string, unknown>;
    const binanceApiKey = assertString(binanceObj["api_key"], "exchange.binance.api_key");
    const binanceApiSecret = assertString(binanceObj["api_secret"], "exchange.binance.api_secret");

    // --- binance_futures ---
    const binanceFutures = exObj["binance_futures"];
    if (typeof binanceFutures !== "object" || binanceFutures === null) {
        throw new Error("[config] env.yaml exchange 缺少 binance_futures 配置");
    }
    const futuresObj = binanceFutures as Record<string, unknown>;
    const futuresApiKey = assertString(futuresObj["api_key"], "exchange.binance_futures.api_key");
    const futuresApiSecret = assertString(futuresObj["api_secret"], "exchange.binance_futures.api_secret");

    // --- logging ---
    const logging = obj["logging"];
    if (typeof logging !== "object" || logging === null) {
        throw new Error("[config] env.yaml 缺少 logging 配置段");
    }
    const logObj = logging as Record<string, unknown>;

    const logLevel = assertEnum(logObj["level"], VALID_LOG_LEVELS, "logging.level");
    const nodeEnv = assertEnum(logObj["node_env"], VALID_NODE_ENVS, "logging.node_env");

    return {
        db: {
            host: dbHost,
            port: dbPort,
            name: dbName,
            user: dbUser,
            password: dbPassword,
        },
        redis: {
            url: redisUrl,
            publish_enabled: redisPublishEnabled,
        },
        exchange: {
            binance: {
                api_key: binanceApiKey,
                api_secret: binanceApiSecret,
            },
            binance_futures: {
                api_key: futuresApiKey,
                api_secret: futuresApiSecret,
            },
        },
        logging: {
            level: logLevel,
            node_env: nodeEnv,
        },
    };
}

// ============================================================
// 辅助校验函数
// ============================================================

function assertString(value: unknown, path: string): string {
    if (typeof value !== "string" || value.trim() === "") {
        throw new Error(`[config] ${path} 必须为非空字符串，实际: ${JSON.stringify(value)}`);
    }
    return value;
}

function assertPort(value: unknown, path: string): number {
    if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
        return value;
    }
    if (typeof value === "string" && /^\d+$/.test(value)) {
        const n = parseInt(value, 10);
        if (n > 0 && n <= 65535) return n;
    }
    throw new Error(`[config] ${path} 必须为有效端口号 (1-65535)，实际: ${JSON.stringify(value)}`);
}

function assertBoolean(value: unknown, path: string): boolean {
    if (typeof value === "boolean") return value;
    if (value === "true" || value === "false") return value === "true";
    throw new Error(`[config] ${path} 必须为 boolean，实际: ${JSON.stringify(value)}`);
}

function assertEnum<T extends readonly string[]>(
    value: unknown,
    allowed: T,
    path: string,
): T[number] {
    const s = String(value);
    if ((allowed as readonly string[]).includes(s)) {
        return s as T[number];
    }
    throw new Error(
        `[config] ${path} 必须为 ${allowed.join(" | ")} 之一，实际: ${JSON.stringify(value)}`,
    );
}
