/* eslint-disable no-console */

// ==== Типы ====

export type Config = {
    rpcUrl: string;          // RPC CometBFT endpoint
    from: number;            // начальная высота (включительно)
    to: number;              // конечная высота (включительно)
    shards: number;          // всего шардов
    shardId: number;         // id текущего шарда [0..shards-1]
    concurrency: number;     // суммарный параллелизм запросов
    timeoutMs: number;       // таймаут HTTP-запроса
    rps: number;             // целевой rate limit (req/s) на процесс
    retries: number;         // количество повторов сетевых запросов
    backoffMs: number;       // стартовый бэкофф
    backoffJitter: number;   // 0..1 (процент случайного джиттера)
    logLevel: "info" | "debug";
    caseMode: "snake" | "camel";
    progressEveryBlocks: number;   // e.g. 1000
    progressIntervalSec: number;   // e.g. 20
    sinkKind: SinkKind;
    outPath?: string;        // for file sink
    flushEvery?: number;     // batching for sinks

    // PG
    pg?: {
        connectionString?: string;
        host?: string;
        port?: number;
        user?: string;
        password?: string;
        database?: string;
        ssl?: boolean;
        mode?: "block-atomic" | "batch-insert";
        batchBlocks?: number;
        batchTxs?: number;
        batchMsgs?: number;
        batchEvents?: number;
        batchAttrs?: number;
    };

    // Спец-флаг: если true, to надо будет резолвить через /status позже
    resolveLatestTo: boolean;
};

// ==== Мини-loader .env (без зависимостей) ====

import fs from "node:fs";
import path from "node:path";
import { SinkKind } from "./sink/types.js";

function loadDotEnvIfPresent() {
    const envPath = path.resolve(process.cwd(), ".env");
    if (!fs.existsSync(envPath)) return;
    const lines = fs.readFileSync(envPath, "utf8").split(/\r?\n/);
    for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith("#")) continue;
        const eq = trimmed.indexOf("=");
        if (eq <= 0) continue;
        const key = trimmed.slice(0, eq).trim();
        let value = trimmed.slice(eq + 1).trim();
        // Снимаем кавычки, если есть
        if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
            value = value.slice(1, -1);
        }
        if (!(key in process.env)) {
            process.env[key] = value;
        }
    }
}

// ==== Аргументы CLI ====

type ArgMap = Record<string, string | boolean>;

function parseArgv(argv = process.argv.slice(2)): ArgMap {
    const out: ArgMap = {};
    for (const arg of argv) {
        if (!arg.startsWith("--")) continue;
        const body = arg.slice(2);
        const eq = body.indexOf("=");
        if (eq === -1) {
            out[body] = true;
        } else {
            const k = body.slice(0, eq);
            const v = body.slice(eq + 1);
            out[k] = v;
        }
    }
    return out;
}

function asInt(name: string, v: unknown, def?: number): number {
    if (v === undefined || v === null || v === "" || (typeof v === "string" && v.toLowerCase() === "undefined")) {
        if (def === undefined) throw new Error(`Missing required numeric option: ${name}`);
        return def;
    }
    const n = typeof v === "number" ? v : Number(v);
    if (!Number.isFinite(n) || !Number.isInteger(n)) throw new Error(`Option ${name} must be an integer, got "${v}"`);
    if (!Number.isSafeInteger(n)) throw new Error(`Option ${name} exceeds JS safe integer: ${n}`);
    return n;
}

function asPositiveInt(name: string, v: unknown, def?: number): number {
    const n = asInt(name, v, def);
    if (n < 0) throw new Error(`Option ${name} must be >= 0, got ${n}`);
    return n;
}

function asString(name: string, v: unknown, def?: string): string {
    if (v === undefined || v === null || v === "") {
        if (def === undefined) throw new Error(`Missing required option: ${name}`);
        return def;
    }
    return String(v);
}

function asLogLevel(v: unknown, def: "info" | "debug" = "info"): "info" | "debug" {
    const s = (v === undefined || v === null || v === "") ? def : String(v).toLowerCase();
    return s === "debug" ? "debug" : "info";
}

// ==== Главный билд конфигурации ====

export function getConfig(): Config {
    loadDotEnvIfPresent();
    const args = parseArgv();

    // RPC URL
    const rpcUrl = asString("RPC_URL", args.rpcUrl ?? process.env.RPC_URL ?? "http://127.0.0.1:26657");

    // from / to
    // Поддерживаем --to=latest -> resolveLatestTo=true (to временно = 0)
    const fromRaw = args.from ?? process.env.FROM;
    const toRaw = args.to ?? process.env.TO;

    const resolveLatestTo = typeof toRaw === "string" && toRaw.toLowerCase() === "latest";

    const from = asPositiveInt("from", fromRaw ?? 1); // по умолчанию стартуем с 1
    const to = resolveLatestTo ? 0 : asPositiveInt("to", toRaw ?? from); // если не задан, обрабатываем один блок

    // Шардинг
    const shards = asPositiveInt("shards", args.shards ?? process.env.SHARDS ?? 1);
    const shardId = asPositiveInt("shard-id", args["shard-id"] ?? process.env.SHARD_ID ?? 0);
    if (shards <= 0) throw new Error(`shards must be >= 1, got ${shards}`);
    if (shardId < 0 || shardId >= shards) throw new Error(`shard-id must be in [0..${shards - 1}], got ${shardId}`);

    // Параллелизм и таймауты
    const concurrency = asPositiveInt("concurrency", args.concurrency ?? process.env.CONCURRENCY ?? 48);
    const timeoutMs = asPositiveInt("timeout-ms", args["timeout-ms"] ?? process.env.TIMEOUT_MS ?? 5000);
    const rps = asPositiveInt("rps", args.rps ?? process.env.RPS ?? 150);
    const retries = asPositiveInt("retries", args.retries ?? process.env.RETRIES ?? 3);
    const backoffMs = asPositiveInt("backoff-ms", args["backoff-ms"] ?? process.env.BACKOFF_MS ?? 250);
    const backoffJitter = Number(args["backoff-jitter"] ?? process.env.BACKOFF_JITTER ?? 0.3);
    if (!(backoffJitter >= 0 && backoffJitter <= 1)) throw new Error(`backoff-jitter must be in [0..1], got ${backoffJitter}`);

    const logLevel = asLogLevel(args["log-level"] ?? process.env.LOG_LEVEL ?? "info");

    // Базовые логические проверки
    if (!rpcUrl.startsWith("http://") && !rpcUrl.startsWith("https://")) {
        throw new Error(`RPC_URL must start with http:// or https://, got "${rpcUrl}"`);
    }
    if (!resolveLatestTo && to < from) {
        throw new Error(`to (${to}) must be >= from (${from})`);
    }

    const caseMode = (args.case as string | undefined)?.toLowerCase() === "camel" ? "camel" : "snake";
    const progressEveryBlocks = Math.max(1, Number(args["progress-every-blocks"] ?? 1000));
    const progressIntervalSec = Math.max(1, Number(args["progress-interval-sec"] ?? 2));
    const sinkKind = (String(args["sink"] ?? "stdout") as SinkKind);
    const outPath = args["out"] ? String(args["out"]) : undefined;
    const flushEvery = args["flush-every"] ? Number(args["flush-every"]) : undefined;

    return {
        rpcUrl,
        from,
        to,
        shards,
        shardId,
        concurrency,
        timeoutMs,
        rps,
        retries,
        backoffMs,
        backoffJitter,
        logLevel,
        resolveLatestTo,
        caseMode,
        progressEveryBlocks,
        progressIntervalSec,
        sinkKind,
        outPath,
        flushEvery,

        pg: {
            connectionString: process.env.DATABASE_URL ?? args["pg-url"],
            host: args["pg-host"],
            port: args["pg-port"] ? Number(args["pg-port"]) : undefined,
            user: args["pg-user"],
            password: args["pg-pass"],
            database: args["pg-db"],
            ssl: args["pg-ssl"] ? true : false,
            mode: (args["pg-mode"] as any) ?? "batch-insert",
            batchBlocks: args["pg-batch-blocks"] ? Number(args["pg-batch-blocks"]) : 1000,
            batchTxs: args["pg-batch-txs"] ? Number(args["pg-batch-txs"]) : 2000,
            batchMsgs: args["pg-batch-msgs"] ? Number(args["pg-batch-msgs"]) : 5000,
            batchEvents: args["pg-batch-events"] ? Number(args["pg-batch-events"]) : 5000,
            batchAttrs: args["pg-batch-attrs"] ? Number(args["pg-batch-attrs"]) : 10000,
        }
    };
}

// Удобный pretty-print (опционально)
export function printConfig(cfg: Config) {
    const view = {
        rpcUrl: cfg.rpcUrl,
        from: cfg.from,
        to: cfg.resolveLatestTo ? "latest" : cfg.to,
        shards: `${cfg.shardId + 1}/${cfg.shards}`,
        concurrency: cfg.concurrency,
        timeoutMs: cfg.timeoutMs,
        rps: cfg.rps,
        retries: cfg.retries,
        backoffMs: cfg.backoffMs,
        backoffJitter: cfg.backoffJitter,
        logLevel: cfg.logLevel,
        progress_every_blocks: cfg.progressEveryBlocks,
        progress_interval_sec: cfg.progressIntervalSec,
        sink: cfg.sinkKind,
        out: cfg.outPath ?? "-",
        flush_every: cfg.flushEvery ?? 1,
        pg: cfg.pg ? undefined : "disabled",
    };
    console.log("[config]", JSON.stringify(view, null, 2));
}