import { countReset } from "console";
import { DefaultEventsMap, Server, Socket } from "socket.io";
export enum SocketEvent {
    BetPlaced = "bet-placed",
    BetConfirmed = "bet-confirmed",
    GetBetAmount = "get-bet-amount",
    SetBetAmount = "set-bet-amount",
    RemoveBetAmount = "remove-bet-amount",
    SetAutoCashout = "set-auto-cashout",
    SetAutoCashout50 = "set-auto-cashout50",
    AutoCashoutCalled = "auto-cashout-called",
    AutoCashout50Called = "auto-cashout50-called",
    Cashout = "cashout",
    Cashout50 = "cashout50",
    GetBalance = "get-balance",
    SetBalance = "set-balance",
    BalanceResponse = "balance-response",
    BetAmountResponse = "bet-amount-response",
    GetCurrentGameState = "get-current-gamestate",
    GetCurrentMultiplierResult = "get-current-multiplier-result",
    CurrentGameStateResponse = "current-gamestate-response",
    GameWaitingTime = "game-waiting-time",
    OnWaiting = "on-waiting",
    OnGameStart = "on-game-start",
    OnPlaying = "on-playing",
    OnGameComplete = "on-game-complete",
    GetTargetResult = "get-target-result",
}
export enum GameState {
    WAITING = "waiting",
    PLAYING = "playing",
    MAXWIN = "maxwin"
}
export interface PlayerData {
    id: string;
    sessionId: string;
    balance: number;
    betAmount: number;
    autoCashoutValue: number;
    autoCashout50Value: number;
    isCashout: boolean;
    isCashout50: boolean;
    reward: number;
    totalCashout: number;
}

const waitingCountdown: number = 12;
let server = new Server(8000);
let currentPlayerBalance: number = 100_000;
let sessionId: string = "";
let sequenceNumberByClient = new Map();

function getRandomRange(min: number, max: number): number {
    return Math.random() * (max - min) + min;
}
function generateSessionId(): string {
    return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
let interate: number = 0;
function randomizeResultNumber(): number {
    // let randomProb: number[] = [10, 20, 80, 2];
    let multiplicationList = [[1.01, 5.99], [10.00, 11.00], [25.00, 30.00], [42.00, 50.00], [81.00, 130.00], [250.00, 5000.00]];
    let randomIdx: number = getRandomRange(0, 100);
    let randomRange: number[] = [];
    let percentage: number = 0;
    // for (let i = 0; i < randomProb.length; i++) {
    //     percentage += randomProb[i];

    //     if (randomIdx >= ((i == 0) ? 0 : percentage - randomProb[i]) && randomIdx < percentage) {
    //         randomRange = multiplicationList[i];
    //     }
    // }
    randomRange = multiplicationList[interate];
    interate++;
    interate = interate % multiplicationList.length;
    let result = parseFloat(getRandomRange(randomRange[0], randomRange[1]).toFixed(2));
    console.log("Random Result: " + result);
    return result;
}
sessionId = generateSessionId();
// event fired every time a new client connects:
server.on("connection", (socket: Socket<DefaultEventsMap, DefaultEventsMap, DefaultEventsMap, any>) => {
    console.info(`Client connected [id=${socket.id}]`);
    // initialize this client's sequence number
    sequenceNumberByClient.set(socket, 1);
    // socket.emit("connection");
    // when socket disconnects, remove it from the list:
    socket.on("disconnect", () => {
        sequenceNumberByClient.delete(socket);
        console.info(`Client gone [id=${socket.id}]`);
    });
    socket.on("message", (text) => {
        console.log("Message from Client: " + text.text);
    });
    // socket.emit(SocketEvent)
    socket.emit(SocketEvent.GetCurrentMultiplierResult, targetResult);
    socket.emit(SocketEvent.GameWaitingTime, waitingCountdown);
    let newBalance: number = 0;
    let idStr: string = socket.id.toString();
    if (playersData.has(idStr)) {
        const player = playersData.get(idStr);
        if (player) {
            if (player.sessionId != sessionId)
                resetBetPlayerData(player);
            newBalance = player?.balance;
        }
    }
    else {
        newBalance = currentPlayerBalance;
    }
    playersData.set(idStr,
        {
            id: idStr,
            sessionId: sessionId,
            balance: newBalance,
            betAmount: 0,
            autoCashoutValue: 0,
            autoCashout50Value: 0,
            isCashout: false,
            isCashout50: false,
            reward: 0,
            totalCashout: 0
        }
    );
    let playerData = playersData.get(idStr);
    //Betting system
    socket.on(SocketEvent.BetPlaced, (amount: number, callback: (success: boolean, message: string) => void) => {
        console.log("[BetPlaced] playerdata: " + playerData + " amount: " + amount);
        if (gameState != GameState.WAITING) {
            callback(false, "Bet sudah ditutup");
            return;
        }
        if (playerData && amount < playerData.balance) {
            playerData.betAmount = amount;
            callback(true, "Success");
        }
        else {
            callback(false, "Balance tidak cukup");
        }
    });
    socket.on(SocketEvent.GetTargetResult, (fn: Function) => {
        if (playerData) {
            // console.log("GetBetAmount: " + playerData.betAmount);
            if (fn)
                fn(targetResult);
            // socket.emit(SocketEvent.BetAmountResponse, playerData.betAmount);
        }
    });

    socket.on(SocketEvent.GetBetAmount, (fn: Function) => {
        if (playerData) {
            console.log("GetBetAmount: " + playerData.betAmount);
            if (fn)
                fn(playerData.betAmount);
            socket.emit(SocketEvent.BetAmountResponse, playerData.betAmount);
        }
    });
    socket.on(SocketEvent.SetBetAmount, (value: number) => {
        if (playerData) {
            playerData.betAmount = value;
        }
    });
    socket.on(SocketEvent.RemoveBetAmount, () => {
        if (playerData) {
            playerData.betAmount = 0;
        }
    });
    //Cashout System
    socket.on(SocketEvent.SetAutoCashout, (value) => {
        if (playerData && gameState != GameState.PLAYING) {
            playerData.autoCashoutValue = value;
        }
    });

    socket.on(SocketEvent.SetAutoCashout50, (value) => {
        if (playerData && gameState != GameState.PLAYING) {
            playerData.autoCashout50Value = value;
        }
    });

    socket.on(SocketEvent.Cashout, (callback: (success: boolean, reward: number) => void) => {
        if (playerData && playerData.betAmount > 0) {
            playerData.isCashout50 = true;
            playerData.isCashout = true;
            if (gameState == GameState.PLAYING) {
                console.log("[SocketEvent] Cashout !!!");
                let reward = playerData.betAmount * currentMultiplier;
                playerData.reward = reward;
                playerData.balance += reward;
                callback(true, reward);
                playerData.betAmount = 0;

            }
        }
    });

    socket.on(SocketEvent.Cashout50, (callback: (success: boolean, reward: number) => void) => {
        if (playerData && playerData.betAmount > 0) {
            playerData.isCashout50 = true;
            if (gameState == GameState.PLAYING) {
                console.log("[SocketEvent] Cashout50 !!!");
                let reward = (playerData.betAmount / 2) * currentMultiplier;
                playerData.reward = reward;
                playerData.balance += reward;
                callback(true, reward);
                playerData.betAmount -= playerData.betAmount / 2;
            }
        }
    });

    socket.on(SocketEvent.GetBalance, (id: string) => {
        // console.log("[GetBalance] playerdata: " + playerData);
        if (playerData)
            socket.emit(SocketEvent.BalanceResponse, playerData?.balance);
    });

    socket.on(SocketEvent.GetCurrentGameState, () => {
        socket.emit(SocketEvent.CurrentGameStateResponse, gameState);
    });

    socket.on(SocketEvent.GetBalance, () => {
        if (playerData) {
            socket.emit(SocketEvent.BalanceResponse, playerData.balance);
        }
    });

    socket.on(SocketEvent.SetBalance, (value: number) => {
        if (playerData) {
            playerData.balance = value;
        }
    });

    if (playerData)
        resetBetPlayerData(playerData);
});

function delay(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

function resetBetPlayerData(playerData: PlayerData) {
    playerData.betAmount = 0;
    playerData.isCashout = false;
    playerData.isCashout50 = false;
    playerData.autoCashout50Value = 0;
    playerData.autoCashoutValue = 0;
    playerData.reward = 0;
    playerData.totalCashout = 0;
}

let lastTime = performance.now();
let gameTime: number = 0;
let currentMultiplier: number = 1.00;
let gameState: GameState = GameState.WAITING;
let playersData: Map<string, PlayerData> = new Map();
let targetResult: number = 0.00;
let isWaiting: boolean = true;
let isPlaying: boolean = true;
let interval: number = 1;

setInterval(async () => {
    let now: number = performance.now();
    let deltaTime: number = (now - lastTime) / 1000; // Convert to seconds
    lastTime = now;
    // console.log("deltaTime: " + deltaTime);
    // currentMultiplier += deltaTime;
    // let idStr = client.id.toString();
    // let playerData = playersData.get(idStr);
    if (gameState == GameState.WAITING && isWaiting) {
        if (gameTime < waitingCountdown) { //Waiting ...
            console.log("[Countdown] gameTime: " + gameTime);
            gameTime += 0.1;
            gameTime = Math.trunc(Math.min(gameTime, waitingCountdown) * 100) / 100; // Round to 2 decimal places
            // console.log("On Counting Down: " + gameTime.toFixed(2));
            for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
                client.emit(SocketEvent.OnWaiting, gameTime);
            }
        }
        else { //Countdown times up !!!
            console.log("[Countdown] Times up !!! go to PLAYING");
            isWaiting = false;
            // console.log("Times up !!! go to PLAYING");
            // setTimeout(() => {
            gameState = GameState.PLAYING;
            for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
                client.emit(SocketEvent.CurrentGameStateResponse, gameState);
            }
            targetResult = randomizeResultNumber();
            for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
                let idStr = client.id.toString();
                let playerData = playersData.get(idStr);
                client.emit(SocketEvent.OnGameStart, targetResult);
                //Check betting
                if (playerData && playerData.betAmount < playerData.balance) {
                    playerData.balance -= playerData.betAmount;
                    playerData.betAmount = 0;
                    client.emit(SocketEvent.BetConfirmed, { balance: playerData.balance });
                }
            }
            // }, 2000);
        }
    }
    else if (gameState == GameState.PLAYING && isPlaying) {
        if (currentMultiplier >= targetResult) { //COMPLETED GAME
            // console.log("On Complete (multiplier): " + currentMultiplier.toFixed(2) + "x");
            console.log("[Playing] Complete targetResult: " + targetResult + " currentMultiplier: " + currentMultiplier.toFixed(2) + "x");
            isPlaying = false;
            // wait 2s for completion animation
            setTimeout(() => {
                console.log("[Playing] Complete go to Countdown !!!");
                for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
                    let isWinTheGame: boolean = false;
                    let idStr = client.id.toString();
                    let playerData = playersData.get(idStr);
                    if (playerData) {
                        isWinTheGame = (playerData.betAmount <= 0 && playerData.totalCashout > 0)
                        client.emit(SocketEvent.OnGameComplete, { betAmount: playerData.betAmount, multiplier: currentMultiplier, win: isWinTheGame });
                        resetBetPlayerData(playerData);
                        sessionId = generateSessionId();
                    }
                }
                gameState = GameState.WAITING;
                isWaiting = true;
                for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
                    client.emit(SocketEvent.CurrentGameStateResponse, gameState);
                }
                isPlaying = true;
                currentMultiplier = 1.00;
                gameTime = 0;
                targetResult = 0;
            }, 6000);
        }
        else {
            interval += 0.001;
            currentMultiplier += (0.01 * interval); // 0.01 = 1% increase per second
            currentMultiplier = Math.min(currentMultiplier, targetResult);
            console.log("[Playing] multiplier: " + currentMultiplier.toFixed(2) + "x");
            // console.log(currentMultiplier.toFixed(2) + "x");
            for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
                let idStr = client.id.toString();
                let playerData = playersData.get(idStr);
                if (playerData && playerData?.betAmount > 0) {
                    //Auto Cashout 50% checking
                    if (playerData.autoCashout50Value > 0 && playerData.betAmount > 0 && currentMultiplier >= playerData.autoCashout50Value) {
                        playerData.betAmount = playerData.betAmount / 2;
                        let wonReward = Math.round(currentMultiplier * playerData.betAmount);
                        // Core.getInstance().coinManager.add(winningCoin);
                        playerData.totalCashout += wonReward;
                        client.emit(SocketEvent.AutoCashout50Called, wonReward);
                        // this.updateCoinUI(true, true);
                        // this.autoCashout50_1TimeCheck = false;
                        // cc.systemEvent.emit(global.EventType.UI.CASHOUT50);
                    }
                    if (playerData.autoCashoutValue > 0 && currentMultiplier >= playerData.autoCashoutValue) {
                        let wonReward = Math.round(currentMultiplier * playerData.betAmount);
                        // Core.getInstance().coinManager.add(wonReward);
                        playerData.totalCashout += wonReward;
                        playerData.isCashout = true;
                        client.emit(SocketEvent.AutoCashoutCalled, wonReward);
                        // this.updateCoinUI(true, true);
                        // this.autoCashout1TimeCheck = false;
                        // cc.systemEvent.emit(global.EventType.UI.CASHOUT);
                    }
                }
            }
        }
        for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
            client.emit(SocketEvent.OnPlaying, currentMultiplier);
        }
    }
    // sequenceNumberByClient.set(client, sequenceNumber + 1);
}, 100);

// sends each client its current sequence number
// setInterval(() => {
//     for (let [client, sequenceNumber] of sequenceNumberByClient.entries()) {
//         client.emit("seq-num", sequenceNumber);
//         sequenceNumberByClient.set(client, sequenceNumber + 1);
//     }
// }, 1000);