import "dotenv/config";
import {Environment} from "./common/environment";
import {TelegramBot, User} from "typescript-telegram-bot-api";
import {Command} from "./base/command";
import {
    delay,
    initSystemSpecs,
    logError,
    processCallbackQuery,
    processEditedMessage,
    processInlineQuery,
    processMyChatMember,
    processNewMessage
} from "./util/utils";
import {Ae} from "./commands/ae";
import {Help} from "./commands/help";
import {Ignore} from "./commands/ignore";
import {Unignore} from "./commands/unignore";
import {Ping} from "./commands/ping";
import {RandomString} from "./commands/random-string";
import {SystemInfo} from "./commands/system-info";
import {Test} from "./commands/test";
import {readData, readPrompts, retrieveAnswers} from "./db/database";
import {Uptime} from "./commands/uptime";
import {WhatBetter} from "./commands/what-better";
import {When} from "./commands/when";
import {RandomInt} from "./commands/random-int";
import {Ban} from "./commands/ban";
import {Quote} from "./commands/quote";
import {Ollama} from "ollama";
import {OllamaSearch} from "./commands/ollama-search";
import {Id} from "./commands/id";
import {OllamaPrompt} from "./commands/ollama-prompt";
import {AdminsAdd} from "./commands/admins-add";
import {AdminsRemove} from "./commands/admins-remove";
import {Shutdown} from "./commands/shutdown";
import {Leave} from "./commands/leave";
import {OllamaChat} from "./commands/ollama-chat";
import {Start} from "./commands/start";
import {GeminiChat} from "./commands/gemini-chat";
import {Choice} from "./commands/choice";
import {Coin} from "./commands/coin";
import {Qr} from "./commands/qr";
import {Distort} from "./commands/distort";
import {Dice} from "./commands/dice";
import {Unban} from "./commands/unban";
import {Title} from "./commands/title";
import {MessageDao} from "./db/message-dao";
import {DatabaseManager} from "./db/database-manager";
import {UserDao} from "./db/user-dao";
import {UserStore} from "./common/user-store";
import {OllamaRequest} from "./model/ollama-request";
import {CallbackCommand} from "./base/callback-command";
import {OllamaCancel} from "./callback_commands/ollama-cancel";
import {MistralChat} from "./commands/mistral-chat";
import {Transliteration} from "./commands/transliteration";
import {OllamaListModels} from "./commands/ollama-list-models";
import {OllamaGetModel} from "./commands/ollama-get-model";
import {OllamaSetModel} from "./commands/ollama-set-model";
import {Mistral} from "@mistralai/mistralai";
import {GoogleGenAI} from "@google/genai";
import {MistralGetModel} from "./commands/mistral-get-model";
import {MistralSetModel} from "./commands/mistral-set-model";
import {MistralListModels} from "./commands/mistral-list-models";
import {GeminiListModels} from "./commands/gemini-list-models";
import {GeminiGetModel} from "./commands/gemini-get-model";
import {GeminiSetModel} from "./commands/gemini-set-model";
import {Debug} from "./commands/debug";
import {GeminiGenerateImage} from "./commands/gemini-generate-image";
import {YouTubeDownload} from "./commands/youtube-download";
import fs from "node:fs";
import path from "node:path";
import {setInterval} from "node:timers";
import {OpenAI} from "openai";
import {OpenAIChat} from "./commands/openai-chat";
import {OpenAIListModels} from "./commands/openai-list-models";
import {OpenAIGetModel} from "./commands/openai-get-model";
import {OpenAISetModel} from "./commands/openai-set-model";
import {Info} from "./commands/info";
import {OpenAIGenImage} from "./commands/openai-gen-image";
import {clearUpFolderFromOldFiles} from "./util/files";
import {DownloadYtVideo} from "./callback_commands/download-yt-video";
import {YtInfo} from "./callback_commands/yt-info";
import {AdminsList} from "./commands/admins-list";
import {ExportDb} from "./commands/export-db";

process.setUncaughtExceptionCaptureCallback(logError);

Environment.load();
DatabaseManager.init();

export const messageDao = new MessageDao();
export const userDao = new UserDao();

export const bot = new TelegramBot({botToken: Environment.BOT_TOKEN, testEnvironment: Environment.TEST_ENVIRONMENT});
export let botUser: User;

export const googleAi = new GoogleGenAI({apiKey: Environment.GEMINI_API_KEY});
export const mistralAi = new Mistral({apiKey: Environment.MISTRAL_API_KEY});
export const openAi = new OpenAI({apiKey: Environment.OPENAI_API_KEY, baseURL: Environment.OPENAI_BASE_URL, dangerouslyAllowBrowser: true});

export const ollama = new Ollama({
    host: Environment.OLLAMA_ADDRESS,
    headers: {"Authorization": `Bearer ${Environment.OLLAMA_API_KEY}`}
});

export const ollamaRequests: OllamaRequest[] = [];

export function getOllamaRequest(uuid: string): OllamaRequest | null {
    return ollamaRequests.find(r => r.uuid === uuid);
}

export function updateOllamaRequest(uuid: string, request: OllamaRequest) {
    const index = ollamaRequests.findIndex(r => r.uuid === uuid);
    if (index >= 0) {
        ollamaRequests[index] = request;
    }
}

export function abortOllamaRequest(uuid: string): boolean {
    const request = getOllamaRequest(uuid);
    if (!request || request.done) return false;

    try {
        request.stream.abort();
        updateOllamaRequest(uuid, {...request, done: true});
        return true;
    } catch (e) {
        logError(e);
        return false;
    }
}

export const commands: Command[] = [
    new Start(),
    new Help(),
    new Test(),
    new Ignore(),
    new Unignore(),
    new Ping(),
    new RandomInt(),
    new RandomString(),
    new SystemInfo(),
    new Uptime(),

    new WhatBetter(),
    new When(),

    new Ban(),
    new Unban(),

    new Quote(),
    new Id(),
    new Choice(),
    new Coin(),
    new Qr(),
    new Distort(),
    new Dice(),
    new Title(),
    new Transliteration(),
    new Debug(),
    new Info(),

    new AdminsAdd(),
    new AdminsRemove(),
    new AdminsList(),

    new ExportDb(),

    new Shutdown(),
    new Leave(),

    new YouTubeDownload()
];

if (Environment.ENABLE_UNSAFE_EVAL) {
    commands.push(new Ae());
}

export const callbackCommands: CallbackCommand[] = [
    new OllamaCancel(),
    new DownloadYtVideo(),
    new YtInfo()
];

if (Environment.OLLAMA_ADDRESS && Environment.OLLAMA_MODEL && Environment.SYSTEM_PROMPT) {
    commands.push(
        new OllamaChat(),
        new OllamaPrompt(),
        new OllamaListModels(),
        new OllamaGetModel(),
        new OllamaSetModel()
    );
}

if (Environment.OLLAMA_API_KEY) {
    commands.push(new OllamaSearch());
}

if (Environment.GEMINI_API_KEY) {
    commands.push(
        new GeminiChat(),
        new GeminiListModels(),
        new GeminiGetModel(),
        new GeminiSetModel(),
        new GeminiGenerateImage()
    );
}

if (Environment.MISTRAL_API_KEY) {
    commands.push(
        new MistralChat(),
        new MistralListModels(),
        new MistralGetModel(),
        new MistralSetModel()
    );
}

if (Environment.OPENAI_API_KEY) {
    commands.push(
        new OpenAIChat(),
        new OpenAIListModels(),
        new OpenAIGetModel(),
        new OpenAISetModel(),
        new OpenAIGenImage()
    );
}

export const cacheDir = path.join(Environment.DATA_PATH, "cache");
export const photoDir = path.join(cacheDir, "photo");
export const photoGenDir = path.join(photoDir, "gen");
export const videoDir = path.join(cacheDir, "video");
export const videoTempDir = path.join(videoDir, "temp");

let isShuttingDown = false;

async function shutdown(signal: NodeJS.Signals) {
    if (isShuttingDown) return;
    isShuttingDown = true;

    console.log(`Received ${signal}. Stopping bot polling...`);

    try {
        await bot.stopPolling();
    } catch (error) {
        logError(error);
    } finally {
        process.exit(0);
    }
}

async function main() {
    const start = Date.now();

    await readPrompts();

    console.log(Environment.SYSTEM_PROMPT);
    
    console.log(
        `TEST_ENVIRONMENT: ${Environment.TEST_ENVIRONMENT}\n` +
        `DATA_PATH: ${Environment.DATA_PATH}\n` +
        `MAX_PHOTO_SIZE: ${Environment.MAX_PHOTO_SIZE}\n` +
        `ONLY_FOR_CREATOR: ${Environment.ONLY_FOR_CREATOR_MODE}\n` +
        `DEFAULT_AI_PROVIDER: ${Environment.DEFAULT_AI_PROVIDER}`
    );

    const dirsToCheck = [cacheDir, photoDir, photoGenDir, videoDir, videoTempDir];
    dirsToCheck.forEach(dir => {
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir);
        }
    });

    const now = new Date();

    const midnight = new Date();
    midnight.setHours(0, 0, 0, 0);
    midnight.setDate(now.getDate() + 1);

    const diff = midnight.getTime() - now.getTime();
    console.log("Clearing up cache will be started in " + diff + "ms");

    clearUpFolderFromOldFiles(cacheDir);
    delay(diff).then(() => {
        setInterval(() => {
            console.log("Started clearing up cache");
            clearUpFolderFromOldFiles(cacheDir);
        }, 1000 * 60 * 60 * 24);
    });

    const cmds = commands.filter(cmd => {
        return cmd.title && cmd.title.startsWith("/") && cmd.title.split(" ").length === 1 && cmd.description;
    }).map(cmd => {
        return {
            command: cmd.title.toLowerCase(),
            description: cmd.description,
        };
    });

    try {
        const results = await Promise.all(
            [
                initSystemSpecs(), readData(), retrieveAnswers(),
                bot.getMe(),
                bot.setMyCommands({commands: cmds, scope: {type: "default"}})
            ]
        );
        botUser = results[3];
        await UserStore.put(botUser);
        await bot.startPolling();

        const end = Date.now();
        const diff = Math.abs(end - start);
        console.log(`Bot started in ${diff}ms!`);
    } catch (error) {
        logError(error);
    }
}

bot.on("my_chat_member", processMyChatMember);
bot.on("edited_message", processEditedMessage);
bot.on("message", processNewMessage);
bot.on("inline_query", processInlineQuery);
bot.on("callback_query", processCallbackQuery);

process.on("SIGTERM", () => {
    shutdown("SIGTERM").catch(logError);
});

process.on("SIGINT", () => {
    shutdown("SIGINT").catch(logError);
});

main().catch(logError);