import { MgbaHttp } from "../modules/gba-api/MgbaHttp";
import { Message } from "../modules/tiktok/Message";
import { WebcastPushConnection } from "tiktok-live-connector";
import { User, FollowerRole } from "../modules/tiktok/User";
import { Gift } from "../modules/tiktok/Gift";
import { Logger, LogLevel } from "../modules/logging/Logging";
import { UserStorage } from "../modules/database/Storage";

const FREE_GAME_INPUT_MOVES = 5;

export class GameController {
    private channelName: string;
    private users: Map<string, User> = new Map<string, User>();
    private webcastPushConnection: WebcastPushConnection;
    private mgbaHttpClient: MgbaHttp = new MgbaHttp({});
    private userStorage: UserStorage;
    private availableGifts: Map<number, Gift> = new Map<number, Gift>();

    /**
     * Create a new GameController object
     * @param {string} channelName The channel name for the TikTok Live connection
     */
    constructor(channelName: string) {
        // Initialize the UserStorage object
        this.userStorage = new UserStorage();
        // Set the channel name and create a new WebcastPushConnection
        this.channelName = channelName;
        this.webcastPushConnection = new WebcastPushConnection(
            this.channelName
        );
        // Set the base URL for the mGBA API client
        this.mgbaHttpClient = new MgbaHttp({
            baseUrl: "http://localhost:5000",
        });
        // Load the available gifts from the TikTok platform and start the GameController
        this.getAvailableGifts().then((gifts) => {
            this.availableGifts = gifts;
            this.startGameController();
        });
    }

    /**
     * Load the available gifts from the TikTok platform
     * @returns {void}
     */
    private async getAvailableGifts(): Promise<Map<number, Gift>> {
        let returnGifts = new Map<number, Gift>();
        let gifts = await this.getWebcastPushConnection().getAvailableGifts();
        gifts.forEach((gift: any) => {
            returnGifts.set(gift.id, new Gift({
                giftId: gift.id,
                costDiamonds: gift.diamond_count,
                name: gift.name,
            }));
        });
        return returnGifts;
    }

    /**
     * Get the WebcastPushConnection object for the TikTok Live connection
     * @returns {WebcastPushConnection} The WebcastPushConnection object for the TikTok Live connection
     */
    private getWebcastPushConnection(): WebcastPushConnection {
        return this.webcastPushConnection;
    }

    /**
     * Get the users Map object, for all users in the TikTok Live that have done some action
     * @returns {Map<string, User>} The Map of users in the TikTok Live
     */
    private getUsers(): Map<string, User> {
        return this.users;
    }

    /**
     * Create a new User object if it does not exist, otherwise return the existing User object
     * @param {string} userId The unique identifier for the user
     * @param {any} data The data object for the user, can be null for searching existing users
     * @returns {User} The User object for the user
     */
    private newUser(userId: string, data: any): User {
        if (this.getUsers().has(userId)) {
            return this.getUsers().get(userId);
        } else {
            let newUser = new User({
                userId: userId,
                displayName: data.nickname,
                username: data.uniqueId,
                isFollower: data.followRole === FollowerRole.FOLLOWER,
                isSubscriber: data.isSubscriber,
            });
            this.getUsers().set(userId, newUser);
            return newUser;
        }
    }

    /**
     * Handle the chat event from the TikTok Live connection
     * @param {any} data The data object for the chat event
     */
    private chatHandler(data: any): void {
        let user = this.newUser(data.userId, data);
        let message = new Message({
            messageId: data.msgId,
            content: data.comment,
        });
        this.chatHandlerForGameInteraction(message, user);
    }

    /**
     * Handle the game input from the chat event for game interaction
     * @param message {Message}
     * @param user {User}
     */
    private chatHandlerForGameInteraction(message: Message, user: User): void {
        let gameKeyInterpretation = message.getMessageGameKey();
        if (gameKeyInterpretation !== false) {
            this.getUserStorage().getUser(user.getUserId()).then((storageUser) => {
                console.log(storageUser);
                if (storageUser === undefined) {
                    this.getUserStorage().createUser(parseInt(user.getUserId()), FREE_GAME_INPUT_MOVES);
                    this.chatHandlerForGameInteraction(message, user);
                    return;
                }
    
                let hasBalance = storageUser.credits > 0 || this.getDeveloperUserIds().includes(parseInt(user.getUserId()));
                if(hasBalance) {
                    // Subtract the credit from the user when the user is not a developer
                    if(!this.getDeveloperUserIds().includes(parseInt(user.getUserId()))) {
                        this.getUserStorage().updateUserCredits(user.getUserId(), storageUser.credits - 1);
                    }
                    // Send the button tap to the mGBA API
                    this.mgbaHttpClient
                    .buttonTapCreate({
                        key: gameKeyInterpretation.toString(),
                    })
                    .then(() => {
                        Logger.logMessage(`Button tap for ${gameKeyInterpretation} created by ${user.getDisplayName()}`, "GameInput");
                    });
                }
            });
        }
    }

    /**
     * Get the user IDs for the developers
     * @returns {number[]} The user IDs for the developers
     */
    private getDeveloperUserIds(): number[] {
        return [
            234457066013368320 // Dennis
        ]
    }

    /**
     * Handle the gift event from the TikTok Live connection
     * @param {any} data The data object for the gift event
     */
    private giftHandler(data: any): void {
        let giftObject = this.availableGifts.get(data.giftId);
        if(giftObject) {
            let giftValue = giftObject.getCostDiamonds();
            this.getUserStorage().getUser(data.userId).then((storageUser) => {
                if(storageUser === undefined) {
                    this.getUserStorage().createUser(parseInt(data.userId), giftValue + FREE_GAME_INPUT_MOVES);
                    return;
                }
                this.getUserStorage().updateUserCredits(data.userId, storageUser.credits + giftValue);
            });
        }
    }

    /**
     * Get the database storage object for the users
     * @returns {UserStorage} The UserStorage object for the GameController
     */
    private getUserStorage(): UserStorage {
        return this.userStorage;
    }

    /**
     * Start the GameController and connect to the TikTok Live
     * @returns {void}
     */
    private startGameController(): void {
        Logger.logMessage(`Trying to connect to the TikTok Livestram: ${this.channelName}`, "GameController", LogLevel.INFO);
        this.getWebcastPushConnection()
            .connect()
            .then(() => {
                Logger.logMessage("Connection successfully established", "WebcastConnection", LogLevel.INFO);
                this.webcastPushConnection.on("chat", this.chatHandler.bind(this));
                this.webcastPushConnection.on("gift", this.giftHandler.bind(this));
            })
            .catch((error) => {
                Logger.logDebug(`The connection could not be established`, error, "WebcastConnection", LogLevel.ERROR);
            });
    }
}
