const express = require('express');
const { exec } = require('child_process');

require('dotenv').config();

const phonesCfg = require('./phones.json');
const buttonsCfg = require('./buttons.json');
const fs = require('fs');
const path = require('path');
const bcrypt = require('bcrypt');

if (!fs.existsSync(path.join(__dirname, 'slingtoken.txt'))) {
	// Create empty file
	fs.writeFileSync(path.join(__dirname, 'slingtoken.txt'), '', 'utf8');
	console.warn('slingtoken.txt file created. Please add your Sling token to this file to use sling messaging!');
}

global.slingToken = fs.readFileSync(path.join(__dirname, 'slingtoken.txt'), 'utf8').trim();
const contexts = {};

// Generate contexts from buttonsCfg
Object.keys(buttonsCfg).forEach(category => {
	buttonsCfg[category].forEach(button => {
		if (button.name && button.context) {
			contexts[button.name] = {
				context: button.context.context,
				timeout: button.context.timeout,
				cid: button.context.cid,
				...(button.context.dial && { dial: button.context.dial }),
				...(button.sling_chat_id && { sling_chat_id: button.sling_chat_id }),
				...(button.sling_chat_message && { sling_chat_message: button.sling_chat_message })
			};
		}
	});
});
//console.log('Generated contexts:', contexts);

function trigCall(pageType, phone, variables = {}) {
	console.log(variables)
	// If contexts[pageType] does not exist, return an error
	if (!contexts[pageType]) {
		throw new Error(`Invalid page type: ${pageType}`);
	}
	const { context, timeout, cid, dial, sling_chat_id, sling_chat_message } = contexts[pageType];
	const targetNumber = dial || phone;
	if (!targetNumber) {
		throw new Error(`Phone number is required for page type: ${pageType}`);
	}

	// Sling message
	if (sling_chat_id && sling_chat_message && global.slingToken) {
		const replacedMessage = typeof sling_chat_message === 'string'
			? sling_chat_message.replace(/%initiatingUser%/g, variables.initiatingUser || '')
			: sling_chat_message;
		fetch(`https://api.getsling.com/v1/conversations/${sling_chat_id}/messages`, {
			method: 'POST',
			headers: {
				'Authorization': `${global.slingToken}`,
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				'content': replacedMessage
			})
		}).then(res => res.json()).then(data => {
			if (data && data.success) {
				console.log('Sling chat message sent successfully.');
			} else {
				console.error('Error sending Sling chat message:', data);
			}
		}).catch(error => {
			console.error('Error sending Sling chat message:', error);
		});
	}

	originateCall(targetNumber, context, 0, timeout, cid, {initiatingUser: variables.username, slingChannel: sling_chat_id || undefined, slingMessage: sling_chat_message || undefined}).then((output) => {
		console.log(`Call originated: ${output}`);
	}).catch((error) => {
		console.error(`Error originating call: ${error}`);
	});
	return true;
}

function originateCall(number, context, delay, timeout, cid, variables = {}) {
	// Build the base command
	let command = `/usr/bin/ast_originate ${number} ${context} ${delay} ${timeout} ${Buffer.from(cid).toString('base64')}`;

	// Add variables if provided
	if (variables && typeof variables === 'object') {
		const varString = Object.entries(variables)
			.map(([key, value]) => `"${key}=${value}"`)
			.join(' ');
		if (varString) {
			command += ` ${varString}`;
		}
	}

	return new Promise((resolve, reject) => {
		exec(command, (error, stdout, stderr) => {
			if (error) {
				reject(error);
			} else {
				resolve(stdout);
			}
		});
	});
}


// Sling chat stuff; Check current token with GET https://api.getsling.com/v1/account/session, refresh with POST https://api.getsling.com/v1/account/session (GET NEW TOKEN FROM RETURN AUTHORIZATION HEADER)
async function slingAuthLoop() {
	console.log("Start slingAuthLoop")
	if (!global.slingToken) {
		console.warn('No Sling token provided.');
		return;
	}
	setTimeout(slingAuthLoop, 15 * 60 * 1000); // Refresh every 15 minutes
	const sessionCheck = await fetch('https://api.getsling.com/v1/account/session', {
		method: 'GET',
		headers: {
			'Authorization': `${global.slingToken}`
		}
	});
	console.log(sessionCheck)
	if (sessionCheck && sessionCheck.ok) {
		console.log('Sling session is valid. Refreshing token...');
		const sessionRefresh = await fetch('https://api.getsling.com/v1/account/session', {
			method: 'POST',
			headers: {
				'Authorization': `${global.slingToken}`
			}
		});
		const newToken = sessionRefresh.headers.get('Authorization');
		if (newToken) {
			global.slingToken = newToken;
			fs.writeFileSync(path.join(__dirname, 'slingtoken.txt'), newToken, 'utf8');
			console.log('Sling token refreshed.');
		}
	} else {
		console.error('Sling session is invalid. Please get a new token manually!');
	}
}

slingAuthLoop();

global.contexts = contexts;
global.trigCall = trigCall;
global.buttonsCfg = buttonsCfg;
global.phonesCfg = phonesCfg;
global.originateCall = originateCall;
global.exec = exec;

const app = express();
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.set('view engine', 'ejs');
app.set('views', './views');
app.use(express.static('static'));
app.use(require('express-session')({
	secret: process.env.SESSION_SECRET || 'fallback-secret-key',
	resave: false,
	saveUninitialized: false,
	cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 },
	store: new (require('session-file-store')(require('express-session')))({
		path: path.join(__dirname, 'sessions'),
		ttl: 24 * 60 * 60, // 1 day
		retries: 1,
		reapInterval: 60 * 60 // prune expired sessions every hour
	}),
}));

function auth(req, res, next) {
	if (!req.session || !req.session.authenticated) {
		return res.redirect('/login');
	}
	if (req.session.user && req.session.user.remember) {
		// Extend session expiration
		req.session.cookie.maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
	}
	next();
}

function apiAuth(req, res, next) {
	console.log(req.headers)
	if (req.headers["authorization"]) {
		if (req.headers["authorization"] === process.env.API_TOKEN) {
			return next();
		} else {
			console.log('Unauthorized API access attempt with invalid token');
			return res.status(401).json({ success: false, message: 'Unauthorized' });
		}
	}
	if (!req.session || !req.session.authenticated) {
		console.log('Unauthorized API access attempt without valid session');
		return res.status(401).json({ success: false, message: 'Unauthorized' });
	}
	if (req.session.user && req.session.user.remember) {
		// Extend session expiration
		req.session.cookie.maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days
	}
	next();
}

global.auth = auth;
global.apiAuth = apiAuth;

// folder where routes live
const ROUTES_DIR = path.join(__dirname, "routes");

// Recursive route loader
function loadRoutes(dir, baseRoute = "") {
	fs.readdirSync(dir).forEach((file) => {
		const fullPath = path.join(dir, file);
		const stat = fs.statSync(fullPath);

		if (stat.isDirectory()) {
			// If folder, recurse deeper
			const nestedBase = baseRoute + "/" + file;
			loadRoutes(fullPath, nestedBase);
		} else if (file.endsWith(".js")) {
			// If .js file → mount route
			const routePath =
				baseRoute +
				"/" +
				file.replace(".js", "").replace("index", "");

			const router = require(fullPath);

			console.log(`Mounting route: ${routePath || "/"}`);
			app.use(routePath || "/", router);
		}
	});
}

// Load everything in routes/
loadRoutes(ROUTES_DIR);


const convertPasswords = () => { // Read auth.json and convert plaintext to hashed passwords
	const authFilePath = path.join(__dirname, 'auth.json');
	if (fs.existsSync(authFilePath)) {
		let authData = JSON.parse(fs.readFileSync(authFilePath, 'utf8'));
		let updated = false;
		authData = authData.map(user => {
			if (user.password) {
				const hashedPassword = bcrypt.hashSync(user.password, 10);
				updated = true;
				return { ...user, passwordHash: hashedPassword, password: undefined };
			}
			return user;
		});
		if (updated) {
			fs.writeFileSync(authFilePath, JSON.stringify(authData, null, 4), 'utf8');
			console.log('Converted plaintext passwords to hashed passwords in auth.json');
		}
	} else {
		console.warn('auth.json file not found for password conversion.');
	}

	

	global.authUsers = JSON.parse(fs.readFileSync(authFilePath, 'utf8'));
};

convertPasswords();

app.listen(PORT, HOST, () => {
	console.log(`Server running on http://${HOST}:${PORT}`);
});