"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const crypto_1 = __importDefault(require("crypto"));
const nodemailer_1 = __importDefault(require("nodemailer"));
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const twilio_1 = __importDefault(require("twilio"));
const client_s3_1 = require("@aws-sdk/client-s3");
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
const client_sns_1 = require("@aws-sdk/client-sns");
const config_1 = __importDefault(require("./config"));
const google_auth_library_1 = require("google-auth-library");
const googleapis_1 = require("googleapis");
const constants_1 = __importDefault(require("./constants"));
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
const path_1 = __importDefault(require("path"));
const https_1 = __importDefault(require("https"));
const http_1 = __importDefault(require("http"));
const client_scheduler_1 = require("@aws-sdk/client-scheduler");
function deleteOldFiles(directory = 'uploads', maxAgeInMinutes = 5) {
    try {
        console.log('🕒  Deleting old files!');
        const currentTime = new Date();
        const maxAge = maxAgeInMinutes * 60 * 1000;
        const files = fs_1.default.readdirSync(directory);
        let deletedCount = 0;
        let totalFreedBytes = 0;
        for (const file of files) {
            const filePath = directory + '/' + file;
            const stats = fs_1.default.statSync(filePath);
            const fileAge = currentTime.getTime() - stats.birthtime.getTime();
            if (fileAge > maxAge) {
                fs_1.default.unlinkSync(filePath);
                console.log(`Deleted: ${filePath}`);
                deletedCount++;
                totalFreedBytes += stats.size;
            }
        }
        console.log(`Total files deleted: ${deletedCount}`);
        console.log(`Total space freed: ${formatBytes(totalFreedBytes)}`);
        console.log('✅  Done');
    }
    catch (err) {
        console.error(err);
    }
}
function formatBytes(bytes) {
    if (bytes === 0)
        return '0B';
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(1024));
    const value = bytes / Math.pow(1024, i);
    return `${value.toFixed(2)}${sizes[i]}`;
}
function getHash(password) {
    const salt = process.env.PASSWORD_SALT;
    const hash = crypto_1.default.pbkdf2Sync(password, salt, 10000, 64, 'sha512');
    return hash.toString('hex');
}
function getToken(user, remember = true) {
    const { _id, auth } = user;
    const expiry = remember ? { expiresIn: '90d' } : { expiresIn: '24h' };
    const payload = { _id: `${_id}`, hash: auth.password.slice(-10) };
    // @ts-ignore
    const token = jsonwebtoken_1.default.sign(payload, process.env.JWT_SECRET, expiry);
    return token;
}
function deleteFile(file) {
    try {
        if (typeof file === 'string') {
            fs_1.default.unlinkSync(file);
            console.log(`File ${file} deleted!`);
        }
        else {
            fs_1.default.unlinkSync(file.path);
            console.log(`File ${file.filename} deleted!`);
        }
    }
    catch (_a) { }
}
function uploadFile(file, cloudFilePath) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            // @ts-ignore
            const fileBuffer = file instanceof Buffer ? file : yield fs_1.default.promises.readFile(file.path);
            const command = new client_s3_1.PutObjectCommand({
                Bucket: process.env.R2_BUCKET,
                Key: cloudFilePath,
                Body: fileBuffer,
                ACL: 'public-read'
            });
            yield config_1.default.R2.send(command);
            return getR2FileUrl(cloudFilePath);
        }
        catch (error) {
            console.log(error);
        }
    });
}
function deleteR2File(path_2) {
    return __awaiter(this, arguments, void 0, function* (path, completeUrl = true) {
        try {
            if (completeUrl) {
                const initial = getR2FileUrl('');
                path = path.slice(initial.length);
            }
            const command = new client_s3_1.DeleteObjectCommand({
                Bucket: process.env.R2_BUCKET,
                Key: path
            });
            yield config_1.default.R2.send(command);
            console.log(`File deleted successfully.`);
        }
        catch (error) {
            console.log(error);
        }
    });
}
function deleteR2FilesWithPrefix(prefix_1) {
    return __awaiter(this, arguments, void 0, function* (prefix, paginationLimit = 500) {
        var _a, _b;
        try {
            let ContinuationToken;
            do {
                const listCommand = new client_s3_1.ListObjectsV2Command({
                    Bucket: process.env.R2_BUCKET,
                    Prefix: prefix,
                    ContinuationToken,
                    MaxKeys: paginationLimit
                });
                const res = yield config_1.default.R2.send(listCommand);
                const keys = (_b = (_a = res.Contents) === null || _a === void 0 ? void 0 : _a.map(obj => ({ Key: obj.Key }))) !== null && _b !== void 0 ? _b : [];
                if (keys.length > 0) {
                    const deleteCommand = new client_s3_1.DeleteObjectsCommand({
                        Bucket: process.env.R2_BUCKET,
                        Delete: { Objects: keys },
                    });
                    yield config_1.default.R2.send(deleteCommand);
                    console.log(`✅ Deleted: ${keys.length} files under "${prefix}"`);
                }
                ContinuationToken = res.IsTruncated ? res.NextContinuationToken : undefined;
            } while (ContinuationToken);
        }
        catch (error) {
            console.log(error);
        }
    });
}
function getR2FileUrl(path) {
    return `${process.env.R2_PUBLIC_URL}/${path}`;
}
function getRandomOtp(digits = 4) {
    if (config_1.default.isDevelopment) {
        return '1234';
    }
    let otp = `${Math.random()}`.slice(-digits);
    return otp;
}
function sendAWSSms(phone, message) {
    return __awaiter(this, void 0, void 0, function* () {
        if (config_1.default.isDevelopment) {
            return;
        }
        const snsClient = new client_sns_1.SNSClient({
            region: process.env.R2_REGION,
            credentials: {
                accessKeyId: process.env.R2_ACCESS_KEY_ID,
                secretAccessKey: process.env.R2_SECRET_ACCESS_KEY
            }
        });
        const command = new client_sns_1.PublishCommand({
            PhoneNumber: phone,
            Message: message
        });
        try {
            const result = yield snsClient.send(command);
            console.log('Message sent with MessageId:', result);
            return result;
            return result.MessageId;
        }
        catch (error) {
            console.error('Error sending SNS message:', error);
            throw error;
        }
    });
}
function sendSms(phone, message) {
    return __awaiter(this, void 0, void 0, function* () {
        if (config_1.default.isDevelopment) {
            return;
        }
        console.log(`✅ SMS Sent: ${phone} - "${message}"`);
        return;
        const twilioClient = (0, twilio_1.default)(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
        const response = yield twilioClient.messages.create({
            to: phone,
            from: process.env.TWILIO_PHONE,
            body: message
        });
        return response;
    });
}
function sendOauthGmail(email, subject, body, html) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            console.log(`✅ Email Sent: ${email} - "${subject}" - "${body || html}"`);
            if (config_1.default.isDevelopment) {
                return;
            }
            const oAuth2Client = new googleapis_1.google.auth.OAuth2(process.env.GMAIL_CLIENT_ID, process.env.GMAIL_CLIENT_SECRET, 'https://developers.google.com/oauthplayground');
            oAuth2Client.setCredentials({ refresh_token: process.env.GMAIL_REFRESH_TOKEN });
            const accessToken = yield oAuth2Client.getAccessToken();
            if (!(accessToken === null || accessToken === void 0 ? void 0 : accessToken.token)) {
                throw new Error('Failed to retrieve access token');
            }
            const transporter = nodemailer_1.default.createTransport({
                service: 'gmail',
                auth: {
                    type: 'OAuth2',
                    user: process.env.GMAIL_SENDER_EMAIL,
                    clientId: process.env.GMAIL_CLIENT_ID,
                    clientSecret: process.env.GMAIL_CLIENT_SECRET,
                    refreshToken: process.env.GMAIL_REFRESH_TOKEN,
                    accessToken: accessToken === null || accessToken === void 0 ? void 0 : accessToken.token
                }
            });
            const info = yield transporter.sendMail({
                from: `${process.env.GMAIL_SENDER_NAME} <${process.env.GMAIL_SENDER_EMAIL}>`,
                to: email,
                subject,
                text: body,
                html
            });
            return {
                ok: info.response.includes('250'),
                response: info.response
            };
        }
        catch (error) {
            console.log(error);
            return { ok: false, response: 'server error' };
        }
    });
}
function encryptAes(message) {
    let cipher = crypto_1.default.createCipheriv('aes-256-cbc', Buffer.from(config_1.default.key), config_1.default.iv);
    let encrypted = cipher.update(message);
    encrypted = Buffer.concat([encrypted, cipher.final()]);
    return encrypted.toString('base64');
}
function decryptAes(cipherText) {
    let encryptedText = Buffer.from(cipherText, 'base64');
    let decipher = crypto_1.default.createDecipheriv('aes-256-cbc', Buffer.from(config_1.default.key), config_1.default.iv);
    let decrypted = decipher.update(encryptedText);
    decrypted = Buffer.concat([decrypted, decipher.final()]);
    return decrypted.toString();
}
function decodeGoogleToken(googleToken, googleClientId) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const client = new google_auth_library_1.OAuth2Client(googleClientId);
            const ticket = yield client.verifyIdToken({
                idToken: googleToken,
                audience: googleClientId
            });
            const payload = ticket.getPayload();
            return payload;
        }
        catch (error) {
            console.log(error);
        }
    });
}
function sendPushNotification(fcmToken, title, body, data) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            // console.log(JSON.stringify({title, body, data}, null ,2));
            data = JSON.stringify(data);
            // ?DOCS: https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#resource:-message
            const res = yield constants_1.default.firebaseAdmin.messaging().send({
                data: { title, body, data },
                notification: {
                    title,
                    body,
                },
                android: {
                    priority: 'high',
                    notification: {
                        sound: 'default',
                        icon: 'ic_notification',
                        color: '#00A9A7',
                        channelId: 'default_channel',
                    },
                },
                apns: {
                    headers: {
                        'apns-priority': '10',
                    },
                    payload: {
                        aps: {
                            sound: 'default',
                            badge: 1,
                            'mutable-content': 1,
                            'content-available': 1,
                        },
                    },
                },
                token: fcmToken,
            });
        }
        catch (error) {
            const errorCodes = ['messaging/invalid-argument', 'messaging/invalid-recipient', 'messaging/invalid-registration-token', 'messaging/registration-token-not-registered'];
            console.log(error);
            if (errorCodes.includes(error.code)) {
                return error;
            }
        }
    });
}
function generateSignedUrl(path_2) {
    return __awaiter(this, arguments, void 0, function* (path, expiresInMinutes = 20) {
        try {
            const command = new client_s3_1.PutObjectCommand({ Bucket: process.env.R2_BUCKET, Key: path });
            // max expiry is 7 days
            const upload = yield (0, s3_request_presigner_1.getSignedUrl)(config_1.default.R2, command, { expiresIn: expiresInMinutes * 60 });
            const download = getR2FileUrl(path);
            return { upload, download };
        }
        catch (error) {
            console.log(error);
        }
    });
}
function validateRecord(data, field, hasStringValues = true, maxLength = 100) {
    try {
        if (typeof data !== 'object') {
            return `${field} must be an object`;
        }
        const keys = Object.keys(data);
        for (const e of keys) {
            const value = data[e];
            if (typeof e !== 'string') {
                return `${e} in keyof ${field} must be a string`;
            }
            if (!e.length) {
                return `${e} in keyof ${field} must have a length of atleast 1`;
            }
            if (e.length > maxLength) {
                return `${e} in keyof ${field} must have a length of atmost ${maxLength}`;
            }
            if (!hasStringValues) {
                continue;
            }
            if (typeof value !== 'string') {
                return `${field}['${e}'] must be a string`;
            }
            if (!value.length) {
                return `${field}['${e}'] must have a length of atleast 1`;
            }
            if (value.length > maxLength) {
                return `${field}['${e}'] must have a length of atmost ${maxLength}`;
            }
        }
    }
    catch (error) {
        console.log(error);
        return 'unknown error occurred';
    }
}
/*
    Requires ffmpeg:
    Mac: "brew install ffmpeg"
    Ubuntu: "sudo apt install ffmpeg"
*/
function compressVideo(input, output) {
    return __awaiter(this, void 0, void 0, function* () {
        return yield new Promise((resolve, reject) => {
            (0, fluent_ffmpeg_1.default)(input)
                // Video settings
                .videoCodec('libx265')
                .outputOptions([
                '-crf 28',
                '-preset slower',
                '-tag:v hvc1'
            ])
                // Audio settings
                .audioCodec('aac')
                .audioBitrate('96k')
                .save(output)
                // Events
                .on('end', () => {
                resolve(true);
            })
                .on('error', (err) => {
                reject(err);
            });
        });
    });
}
function downloadFile(url, filePath) {
    return new Promise((resolve, reject) => {
        try {
            fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
            const protocol = url.startsWith('https') ? https_1.default : http_1.default;
            const fileStream = fs_1.default.createWriteStream(filePath);
            const request = protocol.get(url, response => {
                if (response.statusCode !== 200) {
                    reject(new Error(`Failed to download file. Status: ${response.statusCode}`));
                    return;
                }
                response.pipe(fileStream);
                fileStream.on('finish', () => {
                    fileStream.close();
                    resolve(null);
                });
            });
            request.on('error', err => {
                fs_1.default.unlink(filePath, () => { });
                reject(err);
            });
        }
        catch (error) {
            console.log(error);
            reject(error);
        }
    });
}
function createEventBridgeSchedule(name_1, at_1, payload_1) {
    return __awaiter(this, arguments, void 0, function* (name, at, payload, autoDelete = true) {
        const toSchedulerTime = (date) => {
            const d = new Date(date);
            return (d.getUTCFullYear() +
                "-" +
                String(d.getUTCMonth() + 1).padStart(2, "0") +
                "-" +
                String(d.getUTCDate()).padStart(2, "0") +
                "T" +
                String(d.getUTCHours()).padStart(2, "0") +
                ":" +
                String(d.getUTCMinutes()).padStart(2, "0") +
                ":" +
                String(d.getUTCSeconds()).padStart(2, "0"));
        };
        const command = new client_scheduler_1.CreateScheduleCommand({
            Name: name,
            ScheduleExpression: `at(${toSchedulerTime(at)})`,
            FlexibleTimeWindow: { Mode: 'OFF' },
            ActionAfterCompletion: autoDelete ? 'DELETE' : undefined,
            Target: {
                Arn: process.env.AWS_EVENTBRIDGE_LAMBDA_ARN,
                RoleArn: process.env.AWS_EVENTBRIDGE_ROLE_ARN,
                Input: JSON.stringify(payload)
            }
        });
        const resp = yield config_1.default.schedulerClient.send(command);
        return resp;
    });
}
function deleteEventBridgeSchedule(name) {
    return __awaiter(this, void 0, void 0, function* () {
        const command = new client_scheduler_1.DeleteScheduleCommand({ Name: name });
        const resp = yield config_1.default.schedulerClient.send(command);
        return resp;
    });
}
// createEventBridgeSchedule('job-123', new Date('2025-12-03T17:39:00+05:30'), {message: 'hello world'}, false)
// deleteEventBridgeSchedule('job-123')
const helpers = {
    deleteOldFiles,
    getHash,
    getToken,
    deleteFile,
    uploadFile,
    deleteR2File,
    deleteR2FilesWithPrefix,
    getR2FileUrl,
    generateSignedUrl,
    getRandomOtp,
    sendAWSSms,
    sendSms,
    sendOauthGmail,
    encryptAes,
    decryptAes,
    decodeGoogleToken,
    sendPushNotification,
    validateRecord,
    compressVideo,
    downloadFile,
    createEventBridgeSchedule,
    deleteEventBridgeSchedule
};
exports.default = helpers;
