// const { fetch } = require('undici');
// const express = require('express');
// const fetch = require('node-fetch');
// const btoa = require('btoa');
// const { clientId, clientSecret, port } = require('./config.json');
import express from 'express'
import fetch from 'node-fetch'
import { MongoClient, ServerApiVersion } from 'mongodb'
import { v4 as uuidv4 } from 'uuid'
import { validate as uuidValidate } from 'uuid';
import bodyParser from 'body-parser'
// import formidable from 'formidable'; //Heroku doesn't recognize this

//Bot section (PLACE IN ENV)
import { Client, Intents, MessageEmbed } from 'discord.js';
const token = process.env.token;
const clientId = process.env.clientId;
const clientSecret = process.env.clientSecret;
const port = process.env.PORT || 3000;
const mongouri = process.env.mongouri;
const client = new MongoClient(mongouri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
const connection = client.connect();


const bot = new Client({ 
    intents: [
        Intents.FLAGS.GUILDS,
        Intents.FLAGS.GUILD_MESSAGES,
        Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
        Intents.FLAGS.GUILD_VOICE_STATES,
        Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS,
        Intents.FLAGS.GUILD_PRESENCES,
        Intents.FLAGS.GUILD_MEMBERS,
        Intents.FLAGS.DIRECT_MESSAGES,
        Intents.FLAGS.DIRECT_MESSAGE_REACTIONS,
        Intents.FLAGS.DIRECT_MESSAGE_TYPING,
    ],
    partials: [ 'CHANNEL' ]
});

bot.home_server = process.env.home_server;
bot.on('ready', async () => {console.log('BOT RUNNING'); });
bot.login(token);


async function getJSONResponse(body) {
	let fullBody = '';

	for await (const data of body) {
		fullBody += data.toString();
	}
	return JSON.parse(fullBody);
}


async function setCurrentServer(sid, id) {
	return new Promise((resolve, reject) => {
		try {
			if (id) {
				const obj = {};
				const arr = {text: [], voice: []};
				const guild = bot.guilds.cache.get(id);

				const roles = [];
				guild.roles.cache.filter((role) => { return(!role.managed) }).forEach((role) => {
					let newObj = {};
					newObj.Id = role.id;
					newObj.name = role.name;
					newObj.color = role.hexColor;
					
					roles.push(newObj);
				});

				const channels = guild.channels.cache;
				channels.forEach((channel) => {
					const type = channel.type;
					if (type == 'GUILD_TEXT') {
						arr.text.push({ name: channel.name, id: channel.id });
					} else if (type == 'GUILD_VOICE') {
						arr.voice.push({ name: channel.name, id: channel.id });
					}
				});

				connection.then((client) => {
					const dbo = client.db(id).collection('SETUP');
					dbo.find().toArray(async (err, docs) => {
						const m = new Map();
						m.set('Id', id);

						await Promise.all(docs.map(async (doc) => {
							m.set(doc._id, doc);
						})).then(() => {
							obj['Id'] = id;
							obj['roles'] = JSON.stringify(roles);
							obj['channels'] = JSON.stringify(arr);
							obj['serverSettings'] = JSON.stringify(Object.fromEntries(m));
							
							const dbo = client.db('main').collection('sessions');
							dbo.updateOne({ sessionId: sid }, {$set: { currentServer: JSON.stringify(obj) }});
							resolve(200);
						});
					})
				});
			}
		} catch (err) {
			reject(err);
		}
	});
}



const app = express();
// app.use(express.json());
app.use(express.static('/assets'));
app.use('/CSS', express.static('./CSS'));
app.use('/scripts', express.static('./scripts'));
app.use(bodyParser.urlencoded({ extended: true }));


app.post('/user', async(request, response) => {
	const guilds = JSON.parse(request.headers.guilds);

	const id = request.headers.userid;

	for (let i = 0; i < guilds.length; i ++) {
		const guild = bot.guilds.cache.get(guilds[i].id);
		
		if (guild && guild.ownerId == id) {
			guilds[i].inServer = true;
		} else {
			guilds[i].inServer = false;
		}
	}

	//Add the guilds to the session data
	connection.then((client) => {
		const sessionId = uuidv4();

		//Used for database clearing
		const d = new Date();
		d.setHours(0);
		d.setMinutes(0);
		d.setSeconds(0);

		const dbo = client.db('main').collection('sessions');
		dbo.insertOne({ sessionId: sessionId, userId: id, guilds: JSON.stringify(guilds), currentServer: null, timeStamp: d.getTime() });

		response.send(sessionId);
	});
});


app.post('/logout', async (req, res) => {
	connection.then((client) => {
		const sessionId = req.headers.sessionid;
		const dbo = client.db('main').collection('sessions');
		dbo.deleteOne({ sessionId: sessionId });
	});

	res.sendStatus(200);
});


app.post('/getSessionInfo', async (req, res) => {
	const session = req.headers.session;
	if (session) {
		if (uuidValidate(session)) {
			connection.then((client) => {
				const dbo = client.db('main').collection('sessions');
				dbo.findOne({ sessionId: session }).then((doc) => {
					res.send(doc);
				})
			})
		}
	}
});


app.post('/getServer', async (req, res) => {
	const id = req.headers.servernumber;
	
	//Get info
	connection.then((client) => {
		const dbo = client.db(id).collection('SETUP');
		dbo.find().toArray(async (err, docs) => {
	
			const m = new Map();
			m.set('Id', id);
			await Promise.all(docs.map(async (doc) => {
				m.set(doc._id, doc);
	
			})).then(() => { res.send(JSON.stringify(Object.fromEntries(m))); })
		})
	});
	
	// return res.sendFile('myGuilds.html', { root: '.' });
})


app.get('/getChannels', async (req, res) => {
	const id = req.headers.servernumber;
	const channels = bot.guilds.cache.get(id).channels.cache;
	
	const arr = {text: [], voice: []};

	channels.forEach((channel) => {
		const type = channel.type;
		if (type == 'GUILD_TEXT') {
			arr.text.push({ name: channel.name, id: channel.id });
		} else if (type == 'GUILD_VOICE') {
			arr.voice.push({ name: channel.name, id: channel.id });
		}
	});

	res.send(arr);
});


//Headers: servernumber, sessionid
app.post('/setCurrentServer', async (req, res) => {
	setCurrentServer().then((code) => {
		res.sendStatus(code);
	}).catch((err) => {
		console.error(err);
		res.sendStatus(500);
	});
});


app.get('/getCal', async (req, res) => {
	const userId = req.headers.userid || false;
	const guildId = req.headers.guildid || false;

	connection.then((client) => {
		var times;

		const dbo = client.db('main').collection('reminderKeys');
		dbo.findOne({$or: [ {userId: userId}, {userId: guildId} ]}).then((doc) => {
			if (!doc) { return res.send([[], []]); }
			times = doc.times;
			let tbo = client.db('main').collection('reminders');

			// { $where: function() { return (times.indexOf(this.time) != -1 && this.userId == userId); }}
			// tbo.find({time: {$in: times}}).toArray((err, docs) => { console.log("A", docs); throw 1; });
			
			tbo.find({time: {$in: times}}).toArray((err, docs) => {

				//There's gotta be a better way
				let newdoc = [];
				for (let i = 0; i < docs.length; i ++) {
					newdoc.push({});
					for (let j in docs[i]) {
						if (!isNaN(j) && (docs[i][j].userId == userId || docs[i][j].guildId == guildId)) {
							// console.log(`${docs[i][j].userId} == ${userId}`, `${docs[i][j].guildId} == ${guildId}`);
							newdoc[i][j] = docs[i][j];
						}
					}

					newdoc[i]._id = docs[i]._id;
					newdoc[i].time = docs[i].time;
					newdoc[i].amt = docs[i].amt;

					//If there's nothing on that date, skip
					// if (newdoc[i].amt == 0) { console.log(newdoc[i]); }
				}
				res.send(JSON.stringify([times, newdoc]));
			});
		});
	})
});


//#region HTML gile serving

app.get('/myGuilds', async (req, res) => {
	return res.sendFile('myGuilds.html', { root: 'HTML' });
});


app.get('/dashboard', async (req, res) => {
	return res.sendFile('dashboard.html', { root: 'HTML' });
});


app.get('/premium', async (req, res) => {
	return res.sendFile('premium.html', { root: 'HTML' });
});

app.get('/index', async (req, res) => {
	return res.sendFile('index.html', { root: 'HTML' });
});

app.get('/calEvent', async (req, res) => {
	return res.sendFile('calEvent.html', { root: 'HTML' });
});

app.get('/newCalEvent', async (req, res) => {
	return res.sendFile('newCalEvent.html', { root: 'HTML' });
});

app.get('/calendar', async (req, res) => {
	return res.sendFile('calendar.html', { root: 'HTML' });
});

app.get('/team', async (req, res) => {
	return res.sendFile("team.html", { root: 'HTML' });
});

app.get('/joinedGuild', async (req, res) => {
	res.sendFile('joinedGuild.html', { root: 'HTML' });
});

app.post('/joinedGuild', async (req, res) => {
	// res.sendFile('joinedGuild.html', { root: 'HTML' });
	const { headers } = req;
	const { servernumber, sessionid } = headers;

	connection.then((client) => {
		const dbo = client.db('main').collection('sessions');
		dbo.find().toArray((err, docs) => {
			const guilds = JSON.parse(docs[0].guilds);

			try {
				for (let i = 0; i < guilds.length; i++) {
					if (guilds[i].id == servernumber) {
						guilds[i].inServer = true;
						setCurrentServer(sessionid, servernumber);
						return res.sendStatus(200);
					}
				}
			} catch (err) {
				console.log(err);
				res.sendStatus(500);
			}
		});
	});
});

//#endregion


//#region INCLUDES

app.get('/cal.css', async (req, res) => {
	return res.sendFile('cal.css', { root: 'CSS' });
})

//#endregion

app.post("/suggestion", async (req, res)=> {
	if (req.headers.sessionid == 'null') {
		return res.sendStatus(401);
	}
	
	const suggestion = req.headers.suggestion;
	if (suggestion) {
		connection.then((client) => {
			const dbo = client.db("main").collection("sessions");
			dbo.findOne({ "sessionId": req.headers.sessionid }).then((doc) => {

				if (!doc) {
					return res.sendStatus(410);
				}

				const discId = doc.userId;
				const user = bot.users.cache.get(discId);

				const channel = bot.guilds.cache.get(bot.home_server).channels.cache.get('944330760146534492');
				const embd = new MessageEmbed()
				
				//If the bot doesn't have the user, just put the id in
				var uid, icon;
				if(!user) {
					uid = `<@${discId}>`;
					icon = 'https://github.com/ION606/selmer-bot-website/blob/main/assets/circleOutline.png?raw=true';
				} else {
					uid = `${user.username}#${user.discriminator}`;
					icon = user.displayAvatarURL();
				}

				embd.setAuthor({ name: `${uid}`, url: "", iconURL: icon });
				embd.setTitle(`Selmer Bot Suggestion`);

				embd.setColor("ORANGE")
				.setTimestamp()
				.setDescription(suggestion)
				.setThumbnail('https://github.com/ION606/selmer-bot-website/blob/main/assets/suggestion.png?raw=true')
				.setFooter({ text: 'This suggestion came from the Selmer Bot Website suggestion box'});

				channel.send({ embeds: [embd] })

				res.sendStatus(200);
			});
		});
	}
})

//Stripe stuff
app.get('/.well-known/apple-developer-merchantid-domain-association', async (req, res) => {
	return res.sendFile('apple-developer-merchantid-domain-association', { root: '.' });
});

app.post('/verifypremium', async (req, res) => {
	if (req.headers.userid) {
		const id = req.headers.userid;

		connection.then((client) => {
			const dbo = client.db('main').collection('authorized');
			dbo.findOne({ discordID: id }).then((doc) => {
				if (doc != undefined) {
					res.sendStatus(200);
				} else {
					res.sendStatus(401);
				}
			}).catch((err) => {
				console.error(err); return res.sendStatus(500);
			})
		});
	}
});

app.post('/sendData', async (req, res) => {
	try {
		if (req.headers.reminders) {
			connection.then((client) => {

				const delObjKeys = JSON.parse(req.headers.delobjkeys);
				// const userId = req.headers.userid;
				const dbo = client.db('main').collection('reminders');
				const kbo = client.db('main').collection('reminderKeys');

				//Update the Time object
				for (var i in delObjKeys) {
					var obj = delObjKeys[i];

					dbo.findOne({ time: obj.time }).then((doc) => {
						if (doc) {
							// kbo.findOne({ 'userId': doc[obj.eventInd].userId }).then((doc) => {
							// 	console.log(doc);
							// }); return console.log(obj.time);
							doc.amt --;

							// console.log(doc[obj.eventInd]); return;
							var searchId;
							if (doc[obj.eventInd].userId != null) {
								searchId = doc[obj.eventInd].userId;
							} else if (doc[obj.eventInd].guildId != null) {
								searchId = doc[obj.eventInd].guildId;
							}

							kbo.findOne({ 'userId': searchId }).then((kdoc) => {
								if ((kdoc.times.length - 1) > 0) {
									kbo.updateOne({ 'userId': searchId }, {$pull: { times: obj.time }});
								} else {
									kbo.deleteOne({ 'userId': searchId });
								}
							});

							//If there's nothing left in the list, delete it
							if (doc.amt > 0) {
								delete doc[obj.eventInd];
								// dbo.findOneAndUpdate({ time: obj.time }, newdoc);
								dbo.findOneAndReplace({ time: obj.time }, doc);
							} else {
								dbo.deleteOne({ time: obj.time });
							}
						} // else { console.log("NONE", obj.time ); }

					}).catch((err) =>  {
						console.log("ERR");
						console.error(err)
						res.sendStatus(500);
					});

					// kbo.findOneAndUpdate({ userId: userId }, { $pull: {}})
				}
			}).then(() => {
				res.sendStatus(200);
			});
		} else {
			const pref = JSON.parse(req.headers.serversettings);

			connection.then(async (client) => {
				const dbo = client.db(pref.Id).collection('SETUP');

				await dbo.updateOne({ _id: 'WELCOME' }, {$set: { welcomechannel: pref.WELCOME.welcomechannel, welcomemessage: pref.WELCOME.welcomemessage }});
				await dbo.updateOne({ _id: 'LOG' }, {$set: { keepLogs: pref.LOG.keepLogs, logchannel: pref.LOG.logchannel, severity: pref.LOG.severity }});
				await dbo.updateOne({ _id: 'announcement' }, {$set: { channel: pref.announcement.channel, role: pref.announcement.role }});
			}).then(() => { res.send("DONE"); })
			.catch((err) => {
				console.error(err);
				res.send("FAILED");
			});
		}
	} catch (err) {
		console.error(err);
		res.send("FAILED");
	}
});


//Reminder format = { time: 1212122, event: { guildId: "930148608400035860", userId: "12", name: "Some Generic Name", description: "Some description", offset: "15", link: "https://www.example.com" } }
app.post('/newCalEvent', async (req, res) => {
	if (req.headers.newcalevent) {
		try {
			const obj = JSON.parse(req.headers.newcalevent);
			// console.log(obj.time, typeof obj.time); return;
			var searchId;
			if (obj.event.userId != null) {
				searchId = obj.event.userId;
			} else if (obj.event.guildId != null) {
				searchId = obj.event.guildId;
			} else { return res.sendStatus(400); }

			connection.then((client) => {
				// Update the Key object first to check if the time is already there
				const kbo = client.db('main').collection('reminderKeys');
				kbo.findOne({ 'userId': searchId }).then((doc) => {

					if (doc) {
						if (doc.times.indexOf(obj.time) == -1) {
							kbo.updateOne({ 'userId': searchId }, { $push: { times: obj.time } })
							.catch((err) => { console.error(err); res.sendStatus(500); });
						} else {
							return res.sendStatus(409);
						}
					} else {
						doc = { 'userId': searchId, times: [obj.time] }
						kbo.insertOne(doc);
					}


					//Update the Time object
					const dbo = client.db('main').collection('reminders');
					dbo.findOne({ time: obj.time }).then((doc) => {
						let n = 0;
						if (doc) {
							n = doc.amt;
							doc.amt ++;

							doc[`${n}`] = obj.event;
							dbo.findOneAndReplace({ time: obj.time }, doc);
						} else {
							const d = new Date(Number(obj.time));
							doc = { "0": obj.event, "time": obj.time, "month": d.getMonth(), "amt": 1 }; //Month used for clearing when the calendar month begins (maybe modify the garbage collection with an `else if (day == 1? clear last month)` )
							dbo.insertOne(doc);
						}

						res.sendStatus(200);
					}).catch((err) =>  {
						console.log("ERR");
						console.error(err);
						res.sendStatus(500);
					});
				}).catch((err) =>  {
					console.log("ERR");
					console.error(err);
					res.sendStatus(500);
				});

				// //Make sure there hasn't been an error (header not yet sent);
				// if (res.headersSent) { return console.log("ERROR"); }
			});
		} catch (err) {
			console.error(err);
			return res.sendStatus(500);
		}
	}
});


/*
app.post("/newics", async(req, res) => {
	var fileupload = req.body.filetoupload;
    var form =  new formidable.IncomingForm();
    form.parse(req, function(err, fields, files){
    	 var oldpath = files.fileupload.path;
         var newpath = "C:/Users/itama_za9au7b/Downloads/SBFiles/"+files.fileupload.name;
         fs.rename(oldpath, newpath, function(err){
         	if(err) {
         		console.log(err);
				res.sendStatus(500);
			} else{
                res.write("File Uploaded");
                res.sendStatus(200);
            }
         }); 
    });
});
*/


app.get('/', async ({ query }, response) => {
	const { code } = query;

	if (code) {
		try {
			await fetch('https://discord.com/api/oauth2/token', {
				method: 'POST',
				body: new URLSearchParams({
					client_id: clientId,
					client_secret: clientSecret,
					code,
					grant_type: 'authorization_code',
					redirect_uri: `https://www.selmerbot.com/`,
					scope: 'identify',
				}),
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded',
				},
			}).then(async response => {
				const oauthData = await getJSONResponse(response.body);
				// console.log(oauthData);
			}).catch(err => { console.error(err); });
		} catch (error) {
			// NOTE: An unauthorized token will not throw an error
			// tokenResponseData.statusCode will be 401
			console.error(error);
		}
	}

	return response.sendFile('index.html', { root: 'HTML' });
});


app.get("*",(req,res) => {
    res.sendFile("404.html", {root: 'HTML'});
})

app.listen(port, () => console.log(`App listening on port ${port}`));