/*Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.*/

//Define global crypto variable for altcha
globalThis.crypto = require('node:crypto').webcrypto;

//Define NODE imports
const https = require('https');
const fs = require('fs');

//Define NPM imports
const express = require('express');
const session = require('express-session');
const {createServer } = require('http');
const cookieParser = require('cookie-parser');
const { Server } = require('socket.io');
const path = require('path');
const mongoStore = require('connect-mongo');
const mongoose = require('mongoose');

//Define Local Imports
//Application
const channelManager = require('./app/channel/channelManager');
const pmHandler = require('./app/pm/pmHandler');
//Util
const configCheck = require('./utils/configCheck');
const scheduler = require('./utils/scheduler');
const {errorMiddleware} = require('./utils/loggerUtils');
const sessionUtils = require('./utils/sessionUtils');
//Validator
const accountValidator = require('./validators/accountValidator');
//DB Model
const statModel = require('./schemas/statSchema');
const flairModel = require('./schemas/flairSchema');
const emoteModel = require('./schemas/emoteSchema');
const tokeModel = require('./schemas/tokebot/tokeSchema');
const tokeCommandModel = require('./schemas/tokebot/tokeCommandSchema');
const migrationModel = require('./schemas/user/migrationSchema');
//Controller
const fileNotFoundController = require('./controllers/404Controller');
//Router
//Humie-Friendly
const indexRouter = require('./routers/indexRouter');
const aboutRouter = require('./routers/aboutRouter');
const registerRouter = require('./routers/registerRouter');
const loginRouter = require('./routers/loginRouter');
const profileRouter = require('./routers/profileRouter');
const adminPanelRouter = require('./routers/adminPanelRouter');
const channelRouter = require('./routers/channelRouter');
const newChannelRouter = require('./routers/newChannelRouter');
const passwordResetRouter = require('./routers/passwordResetRouter');
const emailChangeRouter = require('./routers/emailChangeController');
const migrateRouter = require('./routers/migrateRouter');
//Panel
const panelRouter = require('./routers/panelRouter');
//Popup
//const popupRouter = require('./routers/popupRouter');
//Tooltip
const tooltipRouter = require('./routers/tooltipRouter');
//Api
const apiRouter = require('./routers/apiRouter');

//Define Config variables
const config = require('../config.json');
const port = config.port;
const dbUrl = `mongodb://${config.db.user}:${config.db.pass}@${config.db.address}:${config.db.port}/${config.db.database}`;

//Define express
const app = express();

//Define session-store (exported so we can kill sessions from user schema)
module.exports.store = mongoStore.create({mongoUrl: dbUrl});

//define sessionMiddleware
const sessionMiddleware = session({
    secret: config.secrets.sessionSecret,
    resave: false,
    saveUninitialized: false,
    store: module.exports.store,
    cookie: {
        sameSite: "strict",
        secure: config.protocol.toLowerCase() == "https"
    } 
});

//Declare web server
let webServer = null;

//If we're using HTTPS
if(config.protocol.toLowerCase() == "https"){
    try{
        //Read key/cert files and store contents
        const httpsOptions = {
            key: fs.readFileSync(config.ssl.key),
            cert: fs.readFileSync(config.ssl.cert)
        };

        //Start HTTPS Server
        webServer = https.createServer(httpsOptions, app);
    }catch(err){
        //If the error has a path
        if(err.path != null && err.path != ""){
            //Tell the user to fix their shit
            console.log(`Error opening key/cert file: ${err.path}`);
        //Otherwise
        }else{
            //Shit our pants
            console.log("Unknown error occured while starting HTTPS server! Bailing out!");
            console.log(err);
        }

        //and run for the hills
        process.exit();
    }

//Otherwise
}else{
    //Default to HTTP
    webServer = createServer(app)
}
const io = new Server(webServer, {});

//Connect mongoose to the database
mongoose.set("sanitizeFilter", true).connect(dbUrl).then(() => {
    console.log("Connected to DB");
}).catch((err) => {
    console.error("Unable to connecto to DB: ");
    console.error(err);
    process.exit();
});

//Static File Server, set this up first to avoid middleware running on top of it
//Serve client-side libraries
app.use('/lib/bootstrap-icons',express.static(path.join(__dirname, '../node_modules/bootstrap-icons'))); //Icon set
app.use('/lib/altcha',express.static(path.join(__dirname, '../node_modules/altcha/dist_external'))); //Self-Hosted PoW-based Captcha
app.use('/lib/hls.js',express.static(path.join(__dirname, '../node_modules/hls.js/dist'))); //HLS Media Handler
//Server public 'www' folder
app.use(express.static(path.join(__dirname, '../www')));

//Set View Engine
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');

//Middlware
//Enable Express
app.use(express.json());

//Enable Express Ccokie-Parser
app.use(cookieParser());

//Enable Express-Sessions
app.use(sessionMiddleware);

//Enable Express-Session w/ Socket.IO
io.engine.use(sessionMiddleware);

//Use rememberMe validators accross all requests.
app.use(accountValidator.rememberMeID());
app.use(accountValidator.rememberMeToken());

//Use remember me middleware
app.use(sessionUtils.rememberMeMiddleware);

//Routes
//Humie-Friendly
app.use('/', indexRouter);
app.use('/about', aboutRouter);
app.use('/register', registerRouter);
app.use('/login', loginRouter);
app.use('/profile', profileRouter);
app.use('/adminPanel', adminPanelRouter);
app.use('/c', channelRouter);
app.use('/newChannel', newChannelRouter);
app.use('/passwordReset', passwordResetRouter);
app.use('/emailChange', emailChangeRouter);
app.use('/migrate', migrateRouter);
//Panel
app.use('/panel', panelRouter);
//tooltip
app.use('/tooltip', tooltipRouter);
//Bot-Ready
app.use('/api', apiRouter);

//Handle error checking
app.use(errorMiddleware);

//Basic 404 handler
app.use(fileNotFoundController);

asyncKickStart();

/*Asyncronous Kickstarter function
Allows us to force server startup to wait on the DB to be ready.
Might be better if she kicked off everything at once, and ran a while loop to check when they where all done.
This runs once at server startup, and most startups will run fairly quickly so... Not worth it?*/
async function asyncKickStart(){
    //Lettum fuckin' know wassup
    console.log(`${config.instanceName}(Powered by Canopy) is booting up!`);

    //Run legacy migration
    await migrationModel.ingestLegacyDump();

    //Build migration cache
    await migrationModel.buildMigrationCache();

    //Calculate Toke Map
    await tokeModel.calculateTokeMap();

    //Load default toke commands
    await tokeCommandModel.loadDefaults();

    //Load default flairs
    await flairModel.loadDefaults();

    //Load default emotes
    await emoteModel.loadDefaults();

    //Kick off scheduled-jobs
    scheduler.kickoff();

    //Check for insecure config
    configCheck.securityCheck();

    //Increment launch counter
    await statModel.incrementLaunchCount();

    //Hand over general-namespace socket.io connections to the channel manager
    module.exports.channelManager = new channelManager(io)
    module.exports.pmHandler = new pmHandler(io, module.exports.channelManager);

    //Listen Function
    webServer.listen(port, () => {
        console.log(`Tokes up on port \x1b[4m\x1b[35m${port}\x1b[0m!\n`);
    });
}