"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "ReportRunner", {
    enumerable: true,
    get: function() {
        return ReportRunner;
    }
});
const _config = /*#__PURE__*/ _interop_require_default(require("config"));
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _datefns = require("date-fns");
const _clients3 = /*#__PURE__*/ _interop_require_wildcard(require("@aws-sdk/client-s3"));
const _mkdirp = /*#__PURE__*/ _interop_require_default(require("mkdirp"));
const _ms = /*#__PURE__*/ _interop_require_default(require("ms"));
const _constants = require("@tamanu/constants");
const _reports = require("@tamanu/shared/reports");
const _createNamedLogger = require("@tamanu/shared/services/logging/createNamedLogger");
const _sleepAsync = require("@tamanu/utils/sleepAsync");
const _files = require("../utils/files");
const _localisation = require("../localisation");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {
        __proto__: null
    };
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
const REPORT_RUNNER_LOG_NAME = 'ReportRunner';
let ReportRunner = class ReportRunner {
    async validate(reportModule, reportDataGenerator) {
        const localisation = await (0, _localisation.getLocalisation)();
        if (this.recipients.email && !_config.default.mailgun.from) {
            throw new Error('ReportRunner - Email config missing');
        }
        const { disabledReports } = localisation;
        if (disabledReports.includes(this.reportId)) {
            throw new Error(`ReportRunner - Report "${this.reportId}" is disabled`);
        }
        if (!reportModule || !reportDataGenerator) {
            throw new Error(`ReportRunner - Unable to find report generator for report "${this.reportId}"`);
        }
    }
    /**
   *
   * @returns {Promise<string>}
   */ async getRequestedByUser() {
        return this.store.models.User.findByPk(this.userId);
    }
    /**
   *
   * @returns {Promise<string[][]>}
   */ async getMetadata() {
        const user = await this.getRequestedByUser();
        const date = (0, _datefns.format)(new Date(), 'dd/MM/yyyy');
        const reportName = await this.getReportName();
        const filterString = Object.entries(this.parameters || []).map(([key, value])=>`${key}: ${value}`).join(', ');
        return [
            [
                'Report Name:',
                reportName
            ],
            [
                'Date Generated:',
                date
            ],
            [
                'User:',
                user.email
            ],
            [
                'Filters:',
                filterString
            ]
        ];
    }
    async run() {
        const reportModule = await (0, _reports.getReportModule)(this.reportId, this.store.models);
        const reportDataGenerator = reportModule?.dataGenerator;
        await this.validate(reportModule, reportDataGenerator);
        let reportData = null;
        let metadata = [];
        let reportDuration = 0;
        const { duration, ifRunAtLeast } = this.sleepAfterReport;
        const startTime = Date.now();
        try {
            this.log.info('Running report', {
                parameters: this.parameters
            });
            reportData = await reportModule.dataGenerator({
                ...this.store,
                reportSchemaStores: this.reportSchemaStores
            }, this.parameters);
            metadata = await this.getMetadata();
            this.log.info('Running report finished', {
                parameters: this.parameters
            });
        } catch (e) {
            this.log.error('Error running report', {
                stack: e.stack,
                parameters: this.parameters
            });
            if (this.recipients.email) {
                await this.sendErrorToEmail(e);
            }
            throw new Error(`${e.stack}\nReportRunner - Failed to generate report`);
        } finally{
            reportDuration = Date.now() - startTime;
        }
        try {
            await this.sendReport({
                data: reportData,
                metadata
            });
        } catch (e) {
            throw new Error(`${e.stack}\nReportRunner - Failed to send`);
        } finally{
            // if report took longer than X ms, sleep for Y ms
            if (reportDuration > (0, _ms.default)(ifRunAtLeast)) {
                this.log.info('Sleep after report run', {
                    reportDuration,
                    duration,
                    ifRunAtLeast
                });
                await (0, _sleepAsync.sleepAsync)((0, _ms.default)(duration));
            }
        }
    }
    /**
   * @param request ReportRequest
   * @param reportData []
   * @returns {Promise<void>}
   */ async sendReport(reportData) {
        let sent = false;
        if (this.recipients.email) {
            await this.sendReportToEmail(reportData);
            sent = true;
        }
        if (this.recipients.s3) {
            await this.sendReportToS3(reportData);
            sent = true;
        }
        if (this.recipients.local) {
            await this.sendReportToLocal(reportData);
            sent = true;
        }
        if (!sent) {
            throw new Error('ReportRunner - No recipients');
        }
    }
    /**
   * @param reportData
   * @returns {Promise<void>}
   */ async sendReportToLocal(reportData) {
        const reportName = await this.getReportName();
        for (const recipient of this.recipients.local){
            const { format, path: reportFolder } = recipient;
            if (!format || !reportFolder) {
                const str = JSON.stringify(recipient);
                throw new Error(`ReportRunner - local recipients must specify a format and a path, got: ${str}`);
            }
            await (0, _mkdirp.default)(reportFolder);
            const reportNameExtended = `${reportName}.${format}`;
            const reportPath = _path.default.resolve(reportFolder, reportNameExtended);
            const outputPath = await (0, _files.writeToSpreadsheet)(reportData, reportPath, format);
            // eslint-disable-next-line no-console
            console.log(outputPath);
        }
    }
    /**
   *
   * @returns {Promise<string>}
   */ async getReportName() {
        const { country } = await (0, _localisation.getLocalisation)();
        let reportName = this.reportId;
        const dbDefinedReportModule = await this.store.models.ReportDefinitionVersion.findByPk(this.reportId, {
            include: [
                'reportDefinition'
            ]
        });
        if (dbDefinedReportModule) {
            reportName = `${dbDefinedReportModule.reportDefinition.name}`;
        }
        const date = (0, _datefns.format)(new Date(), 'ddMMyyyy');
        const dashedName = `${reportName}-${country.name}`.trim().replace(/\s+/g, '-').replace(/-+/g, '-').toLowerCase();
        return `report-${date}-${dashedName}`;
    }
    /**
   * @param request ReportRequest
   * @param reportData []
   * @param emailAddresses string[]
   * @returns {Promise<void>}
   */ async sendReportToEmail(reportData) {
        const reportName = await this.getReportName();
        let zipFile;
        try {
            zipFile = await (0, _files.createZippedSpreadsheet)(reportName, reportData, this.exportFormat);
            const recipients = this.recipients.email.join(',');
            this.log.info('Sending report', {
                recipients,
                zipFile
            });
            const result = await this.emailService.sendEmail({
                from: _config.default.mailgun.from,
                to: recipients,
                subject: 'Report delivery',
                text: `Report requested: ${reportName}`,
                attachment: zipFile
            });
            if (result.status === _constants.COMMUNICATION_STATUSES.SENT) {
                this.log.info('Mailgun sent report', {
                    recipients,
                    zipFile
                });
            } else {
                this.log.error('Mailgun error', {
                    recipients,
                    stack: result.error
                });
                throw new Error(`ReportRunner - Mailgun error: ${result.error}`);
            }
        } finally{
            if (zipFile) await (0, _files.removeFile)(zipFile);
        }
    }
    async sendErrorToEmail(e) {
        try {
            const user = await this.getRequestedByUser();
            const reportName = await this.getReportName();
            this.emailService.sendEmail({
                from: _config.default.mailgun.from,
                to: user.email,
                subject: 'Failed to generate report',
                message: `Report requested: ${reportName} failed to generate with error: ${e.message}`
            });
        } catch (e2) {
            this.log.error('Issue sending error to email', {
                stack: e2.stack
            });
        }
    }
    /**
   * @param request ReportRequest
   * @param reportData []
   * @returns {Promise<void>}
   */ async sendReportToS3(reportData) {
        const { region, bucketName, bucketPath } = _config.default.s3;
        if (!bucketPath) {
            throw new Error(`bucketPath must be set, e.g. 'au'`);
        }
        let zipFile;
        const bookType = 'csv';
        try {
            const reportName = await this.getReportName();
            zipFile = await (0, _files.createZippedSpreadsheet)(reportName, reportData, bookType);
            const filename = _path.default.basename(zipFile);
            this.log.info('Uploading report to S3', {
                path: `${bucketName}/${bucketPath}/${filename}`,
                region
            });
            const client = new _clients3.S3({
                region
            });
            const fileStream = _fs.default.createReadStream(zipFile);
            await client.send(new _clients3.PutObjectCommand({
                Bucket: bucketName,
                Key: `${bucketPath}/${filename}`,
                Body: fileStream
            }));
            this.log.info('Uploaded report to S3', {
                zipFile
            });
        } finally{
            if (zipFile) await (0, _files.removeFile)(zipFile);
        }
    }
    constructor(reportId, parameters, recipients, store, reportSchemaStores, emailService, userId, exportFormat, sleepAfterReport){
        this.reportId = reportId;
        this.parameters = parameters;
        this.recipients = recipients;
        this.store = store;
        this.reportSchemaStores = reportSchemaStores;
        this.emailService = emailService;
        this.userId = userId;
        this.log = (0, _createNamedLogger.createNamedLogger)(REPORT_RUNNER_LOG_NAME, {
            reportId,
            userId
        });
        // Export format is only used for emailed recipients. Local reports have the export format
        // defined in the recipients object and reports sent to s3 are always csv.
        this.exportFormat = exportFormat;
        if (typeof sleepAfterReport === 'object') {
            this.sleepAfterReport = sleepAfterReport;
        } else {
            // ReportRunner is running in a child Node process spawned via the custom ‘report’ CLI
            // command. In this case, sleepAfterReport is passed as a stringified object.
            // @see ../subCommands/report.js
            try {
                this.sleepAfterReport = JSON.parse(sleepAfterReport);
            } catch (e) {
                throw new SyntaxError('Invalid format for sleepAfterReport. Must be valid JSON with duration and ifRunAtLeast string values. Supported units: ms, s, min, h');
            }
        }
    }
};

//# sourceMappingURL=ReportRunner.js.map