"use strict";
const crypto = require('crypto');

const ranges = [
    { minRange: 1, maxRange: 2, tag: "Code Card" },
    { minRange: 1, maxRange: 8, tag: "Energy" },
    { minRange: 1, maxRange: 81, tag: "Common" },
    { minRange: 1, maxRange: 81, tag: "Common" },
    { minRange: 1, maxRange: 81, tag: "Common" },
    { minRange: 1, maxRange: 81, tag: "Common" },
    { minRange: 1, maxRange: 70, tag: "Uncommon" },
    { minRange: 1, maxRange: 70, tag: "Uncommon" },
    { minRange: 1, maxRange: 70, tag: "Uncommon" },
    { minRange: 1, maxRange: 176, tag: "Reverse Holofoil" },
    { minRange: 1, maxRange: 10000, tag: "slot9" },
    { minRange: 1, maxRange: 176, tag: "Reverse Holofoil" },
    { minRange: 1, maxRange: 36, tag: "Illustration Rare" },
    { minRange: 1, maxRange: 15, tag: "Special Illustration Rare" },
    { minRange: 1, maxRange: 9, tag: "Hyper Rare" },
    { minRange: 1, maxRange: 10000, tag: "slot10" },
    { minRange: 1, maxRange: 25, tag: "Rare , Holofoil" },
    { minRange: 1, maxRange: 17, tag: "Double Rare" },
    { minRange: 1, maxRange: 26, tag: "Ultra Rare" },
];

function createServerSeed() {
    const serverSeed = crypto.randomBytes(32).toString('hex');
    const serverSeedHash = crypto.createHash('sha256')
        .update(serverSeed)
        .digest('hex');
    return { serverSeed, serverSeedHash };
}

function incrementNonce(nonce, offset) {
    let seconds = parseInt(nonce.slice(0, 2), 10);
    let minutes = parseInt(nonce.slice(2, 4), 10);
    let hours   = parseInt(nonce.slice(4, 6), 10);
    let day     = parseInt(nonce.slice(6, 8), 10);
    let month   = parseInt(nonce.slice(8, 10), 10);
    let year    = parseInt(nonce.slice(10, 14), 10);

    seconds += offset;
    while (seconds > 59) {
        seconds -= 60;
        minutes += 1;
        if (minutes > 59) {
            minutes = 0;
            hours += 1;
            if (hours > 23) {
                hours = 0;
                day += 1;
                const isLeapYear = y => (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0);
                const daysInMonth = [
                    31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30,
                    31, 31, 30, 31, 30, 31
                ];
                if (day > daysInMonth[month - 1]) {
                    day = 1;
                    month += 1;
                    if (month > 12) {
                        month = 1;
                        year += 1;
                    }
                }
            }
        }
    }
    return (
        seconds.toString().padStart(2, '0') +
        minutes.toString().padStart(2, '0') +
        hours.toString().padStart(2, '0') +
        day.toString().padStart(2, '0') +
        month.toString().padStart(2, '0') +
        year.toString().padStart(4, '0')
    );
}

async function generateHmacSha512(key, message) {
    const hmac = crypto.createHmac('sha512', key);
    hmac.update(message);
    const buffer = hmac.digest();
    return new Uint8Array(buffer);
}

async function generateRandomNumbers(ranges, serverSeed, clientSeed, nonce) {
    const randomNumbers = [];
    const numbersPerDigest = 16;
    const digestsNeeded = Math.ceil(ranges.length / numbersPerDigest);

    for (let digestIndex = 0; digestIndex < digestsNeeded; digestIndex++) {
        const message = `${clientSeed}:${nonce}:${digestIndex}`;
        const digest = await generateHmacSha512(serverSeed, message);
        let offset = 0;
        for (let i = 0; i < numbersPerDigest && randomNumbers.length < ranges.length; i++) {
            const range = ranges[randomNumbers.length];
            const bytes = digest.slice(offset, offset + 4);
            const view = new DataView(bytes.buffer, bytes.byteOffset, 4);
            const num = view.getUint32(0, false);
            const rand = Math.floor((num / 0x100000000) * (range.maxRange - range.minRange + 1)) + range.minRange;
            randomNumbers.push(rand);
            offset += 4;
        }
    }
    return randomNumbers;
}

async function mapTaggedResults(randomNumbers, ranges, serverSeed, clientSeed, nonce) {
    const seenValues = {};
    const taggedResults = [];

    for (let index = 0; index < randomNumbers.length; index++) {
        if (!ranges[index]) {
            console.error(`No range for index ${index}`);
            continue;
        }
        const { tag = "UNKNOWN", minRange, maxRange } = ranges[index];
        let finalNumber = randomNumbers[index];
        let nonceOffset = 0;

        if (!seenValues[tag]) seenValues[tag] = new Set();

        while (seenValues[tag].has(finalNumber.toString())) {
            nonceOffset++;
            const newNonce = incrementNonce(nonce, nonceOffset);
            const message = `${clientSeed}:${newNonce}`;
            const digest = await generateHmacSha512(serverSeed, message);
            const bytes = digest.slice(0, 4);
            const view = new DataView(bytes.buffer, bytes.byteOffset, 4);
            const newNum = view.getUint32(0, false);
            finalNumber = Math.floor((newNum / 0x100000000) * (maxRange - minRange + 1)) + minRange;
        }
        seenValues[tag].add(finalNumber.toString());
        taggedResults.push({ value: finalNumber.toString(), tag });
    }
    return taggedResults;
}

async function filterResults(taggedResults, ranges) {
    const filteredResults = [];
    const usedValuesByTag = {};

    const getNextCard = tag => {
        if (!usedValuesByTag[tag]) usedValuesByTag[tag] = new Set();
        const cards = taggedResults.filter(i => i.tag === tag);
        for (const c of cards) {
            if (!usedValuesByTag[tag].has(c.value)) {
                usedValuesByTag[tag].add(c.value);
                return c;
            }
        }
        return null;
    };

    const code = taggedResults.find(i => i.tag === "Code Card");
    if (code) filteredResults.push(code);

    const energy = taggedResults.find(i => i.tag === "Energy");
    if (energy) filteredResults.push(energy);

    filteredResults.push(...taggedResults.filter(i => i.tag === "Common").slice(0, 4));
    filteredResults.push(...taggedResults.filter(i => i.tag === "Uncommon").slice(0, 3));

    const rev = getNextCard("Reverse Holofoil");
    if (rev) filteredResults.push(rev);

    const slot9 = taggedResults.find(i => i.tag === "slot9");
    if (slot9) {
        const n = parseInt(slot9.value, 10);
        if (n >= 1 && n <= 8743) {
            const r = getNextCard("Reverse Holofoil"); if (r) filteredResults.push(r);
        } else if (n >= 8744 && n <= 9512) {
            const r = getNextCard("Illustration Rare"); if (r) filteredResults.push(r);
        } else if (n >= 9513 && n <= 9825) {
            const r = getNextCard("Special Illustration Rare"); if (r) filteredResults.push(r);
        } else if (n >= 9826 && n <= 10000) {
            const r = getNextCard("Hyper Rare"); if (r) filteredResults.push(r);
        }
    }

    const slot10 = taggedResults.find(i => i.tag === "slot10");
    if (slot10) {
        const n = parseInt(slot10.value, 10);
        if (n >= 1 && n <= 7904) {
            const r = getNextCard("Rare , Holofoil"); if (r) filteredResults.push(r);
        } else if (n >= 7905 && n <= 9333) {
            const r = getNextCard("Double Rare"); if (r) filteredResults.push(r);
        } else if (n >= 9334 && n <= 10000) {
            const r = getNextCard("Ultra Rare"); if (r) filteredResults.push(r);
        }
    }

    return filteredResults;
}

async function setData(serverSeed, clientSeed, nonce) {
    const { serverSeedHash } = createServerSeed();
    const randomNumbers = await generateRandomNumbers(ranges, serverSeed, clientSeed, nonce);
    const taggedResults = await mapTaggedResults(randomNumbers, ranges, serverSeed, clientSeed, nonce);
    const filteredResults = await filterResults(taggedResults, ranges);
    return {
        success: true,
        randomNumbers,
        result: filteredResults,
        nonce,
        serverSeed,
        serverSeedHash,
        clientSeed
    };
}

module.exports = { setData, ranges };

(async () => {
    try {
        const result = await setData(
            "f11376f08c58342e05e5ecc6cf47bf9b1f0301cce1cd98508a9da1b5969d5f64",
            "testUser",
            "37411131102025"
        );
        console.log(JSON.stringify(result, null, 2));
    } catch (error) {
        console.error("Error generating pack:", error);
    }
})();
