export function runLuckyGiftSimulation(config, input) {
    const users = clampInt(input.users, 1, 20_000);
    const rooms = clampInt(input.rooms, 1, users);
    const costMin = clampInt(input.costMin, 1, 1_000_000_000);
    const costMax = Math.max(costMin, clampInt(input.costMax, costMin, 1_000_000_000));
    const drawsMin = clampInt(input.drawsMin, 0, 10_000_000);
    const drawsMax = Math.max(drawsMin, clampInt(input.drawsMax, drawsMin, 10_000_000));
    const targetRTP = Number(config?.targetRTPPercent || 95) / 100;
    const seed = finiteSeed(input.seed) ?? runtimeSeed();
    const random = createRandom(seed + users * 31 + rooms * 17 + costMin + costMax + drawsMin + drawsMax);

    const rows = [];
    let totalDraws = 0;
    let totalSpend = 0;
    let rawRewardTotal = 0;

    for (let index = 0; index < users; index += 1) {
        const drawCount = randomInt(random, drawsMin, drawsMax);
        const spend = randomSpend(random, drawCount, costMin, costMax);
        const variance = userRTPVolatility(drawCount);
        const rawReward = Math.max(0, Math.round(spend * targetRTP * (1 + normal(random) * variance)));
        const row = {
            drawCount,
            finalIncome: 0,
            initialIncome: 0,
            netIncome: 0,
            reward: rawReward,
            roomId: `room_${randomInt(random, 1, rooms)}`,
            rtp: 0,
            spend,
            userId: `user_${String(index + 1).padStart(4, "0")}`,
        };
        rows.push(row);
        totalDraws += drawCount;
        totalSpend += spend;
        rawRewardTotal += rawReward;
    }

    const controlledRewardTotal = controlledTotalReward(random, totalSpend, totalDraws, targetRTP);
    distributeRewardCorrection(rows, controlledRewardTotal - rawRewardTotal, totalSpend);

    let totalReward = 0;
    let minRTP = Number.POSITIVE_INFINITY;
    let maxRTP = 0;
    let rtpSum = 0;
    let activeUsers = 0;
    for (const row of rows) {
        row.finalIncome = row.reward;
        row.netIncome = row.reward - row.spend;
        row.rtp = row.spend > 0 ? (row.reward * 100) / row.spend : 0;
        totalReward += row.reward;
        if (row.spend > 0) {
            activeUsers += 1;
            rtpSum += row.rtp;
            minRTP = Math.min(minRTP, row.rtp);
            maxRTP = Math.max(maxRTP, row.rtp);
        }
    }

    const overview = {
        activeUsers,
        averageUserRTP: activeUsers > 0 ? rtpSum / activeUsers : 0,
        costMax,
        costMin,
        drawsMax,
        drawsMin,
        maxUserRTP: activeUsers > 0 ? maxRTP : 0,
        minUserRTP: activeUsers > 0 ? minRTP : 0,
        rooms,
        seed,
        targetRTPPercent: targetRTP * 100,
        totalDraws,
        totalNetIncome: totalReward - totalSpend,
        totalReward,
        totalRTP: totalSpend > 0 ? (totalReward * 100) / totalSpend : 0,
        totalSpend,
        users,
    };

    return { csv: toCSV(rows, overview), overview, rows };
}

function controlledTotalReward(random, totalSpend, totalDraws, targetRTP) {
    if (totalSpend <= 0) {
        return 0;
    }
    const scale = Math.max(1, totalDraws / 100_000);
    const maxDeviation = totalDraws >= 1_000_000 ? 0.005 : totalDraws >= 100_000 ? 0.01 : 0.02;
    const deviation = clamp(normal(random) * (0.003 / Math.sqrt(scale)), -maxDeviation, maxDeviation);
    return Math.max(0, Math.round(totalSpend * targetRTP * (1 + deviation)));
}

function distributeRewardCorrection(rows, correction, totalSpend) {
    if (!correction || totalSpend <= 0 || rows.length === 0) {
        return;
    }
    let applied = 0;
    for (const row of rows) {
        const share = Math.round(correction * (row.spend / totalSpend));
        row.reward = Math.max(0, row.reward + share);
        applied += share;
    }
    let remaining = correction - applied;
    let index = 0;
    while (remaining !== 0 && rows.length > 0) {
        const delta = remaining > 0 ? 1 : -1;
        const row = rows[index % rows.length];
        if (delta > 0 || row.reward > 0) {
            row.reward += delta;
            remaining -= delta;
        }
        index += 1;
        if (index > rows.length * 4 && remaining < 0) {
            break;
        }
    }
}

function randomSpend(random, drawCount, costMin, costMax) {
    if (drawCount <= 0) {
        return 0;
    }
    if (drawCount <= 1_000) {
        let total = 0;
        for (let index = 0; index < drawCount; index += 1) {
            total += randomInt(random, costMin, costMax);
        }
        return total;
    }
    const mean = drawCount * ((costMin + costMax) / 2);
    const rangeSize = costMax - costMin + 1;
    const variance = drawCount * ((rangeSize * rangeSize - 1) / 12);
    const value = Math.round(mean + normal(random) * Math.sqrt(Math.max(0, variance)));
    return clampInt(value, drawCount * costMin, drawCount * costMax);
}

function userRTPVolatility(drawCount) {
    if (drawCount <= 0) {
        return 0;
    }
    return Math.min(0.65, 0.36 / Math.sqrt(Math.max(1, drawCount / 100)));
}

function toCSV(rows, overview) {
    const header = [
        "用户ID",
        "房间ID",
        "抽奖次数",
        "初始收入",
        "总消耗",
        "最终收入",
        "最终净收入",
        "RTP",
    ];
    const lines = [
        ["总览", "", overview.totalDraws, 0, overview.totalSpend, overview.totalReward, overview.totalNetIncome, formatRatio(overview.totalRTP)],
        header,
        ...rows.map((row) => [
            row.userId,
            row.roomId,
            row.drawCount,
            row.initialIncome,
            row.spend,
            row.finalIncome,
            row.netIncome,
            formatRatio(row.rtp),
        ]),
    ];
    return lines.map((line) => line.map(csvCell).join(",")).join("\n");
}

function csvCell(value) {
    const text = String(value ?? "");
    return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}

function createRandom(seed) {
    let state = seed >>> 0;
    return () => {
        state += 0x6d2b79f5;
        let value = state;
        value = Math.imul(value ^ (value >>> 15), value | 1);
        value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
        return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
    };
}

function finiteSeed(value) {
    const number = Number(value);
    return Number.isFinite(number) ? Math.round(number) : null;
}

function runtimeSeed() {
    if (globalThis.crypto?.getRandomValues) {
        const values = new Uint32Array(1);
        globalThis.crypto.getRandomValues(values);
        return values[0];
    }
    return Math.round(Date.now() + Math.random() * 0xffffffff);
}

function normal(random) {
    const u1 = Math.max(Number.EPSILON, random());
    const u2 = Math.max(Number.EPSILON, random());
    return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}

function randomInt(random, min, max) {
    return Math.floor(random() * (max - min + 1)) + min;
}

function clampInt(value, min, max) {
    const number = Math.round(Number(value || 0));
    if (!Number.isFinite(number)) {
        return min;
    }
    return Math.min(max, Math.max(min, number));
}

function clamp(value, min, max) {
    return Math.min(max, Math.max(min, value));
}

function formatRatio(value) {
    return Number(value || 0).toFixed(4);
}
