"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get AUDIT_STATUSES () {
        return AUDIT_STATUSES;
    },
    get DHIS2IntegrationProcessor () {
        return DHIS2IntegrationProcessor;
    },
    get ERROR_LOGS () {
        return ERROR_LOGS;
    },
    get INFO_LOGS () {
        return INFO_LOGS;
    },
    get LOG_FIELDS () {
        return LOG_FIELDS;
    },
    get WARNING_LOGS () {
        return WARNING_LOGS;
    }
});
const _config = /*#__PURE__*/ _interop_require_default(require("config"));
const _lodash = require("lodash");
const _undici = require("undici");
const _xlsx = require("xlsx");
const _tasks = require("@tamanu/shared/tasks");
const _logging = require("@tamanu/shared/services/logging");
const _constants = require("@tamanu/constants");
const _fetchWithRetryBackoff = require("@tamanu/api-client/fetchWithRetryBackoff");
const _crypto = require("@tamanu/shared/utils/crypto");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
const arrayOfArraysToCSV = (reportData)=>_xlsx.utils.sheet_to_csv(_xlsx.utils.aoa_to_sheet(reportData));
const INFO_LOGS = {
    SENDING_REPORTS: 'DHIS2IntegrationProcessor: Sending reports to DHIS2',
    PROCESSING_REPORT: 'DHIS2IntegrationProcessor: Processing report',
    SUCCESSFULLY_SENT_REPORT: 'DHIS2IntegrationProcessor: Report sent to DHIS2 successfully'
};
const WARNING_LOGS = {
    INTEGRATION_NOT_CONFIGURED: 'DHIS2IntegrationProcessor: DHIS2 integration not properly configured, skipping',
    REPORT_DOES_NOT_EXIST: "DHIS2IntegrationProcessor: Report doesn't exist, skipping",
    REPORT_HAS_NO_PUBLISHED_VERSION: 'DHIS2IntegrationProcessor: Report has no published version, skipping',
    FAILED_TO_SEND_REPORT: 'DHIS2IntegrationProcessor: Failed to send report to DHIS2'
};
const ERROR_LOGS = {
    ERROR_PROCESSING_REPORT: 'DHIS2IntegrationProcessor: Error processing report'
};
const AUDIT_STATUSES = {
    SUCCESS: 'success',
    FAILURE: 'failure',
    WARNING: 'warning'
};
const LOG_FIELDS = [
    'reportId',
    'status',
    'message',
    'imported',
    'updated',
    'ignored',
    'deleted'
];
let DHIS2IntegrationProcessor = class DHIS2IntegrationProcessor extends _tasks.ScheduledTask {
    getName() {
        return 'DHIS2IntegrationProcessor';
    }
    async logDHIS2Push({ reportId, status, importCount = {}, conflicts = [], message }) {
        const logEntry = await this.context.store.models.DHIS2PushLog.create({
            reportId,
            status,
            message,
            ...conflicts.length > 0 && {
                conflicts
            },
            ...importCount
        });
        return (0, _lodash.pick)(logEntry.get({
            plain: true
        }), LOG_FIELDS);
    }
    /**
   * Gets DHIS2 credentials. Username is stored plaintext (so operators can
   * verify it at a glance). Password is sensitive and runs through a fallback
   * chain: settings secret (DB) → config secret (encrypted file) → plain config.
   */ async getDHIS2Credentials() {
        const dhis2Settings = await this.context.settings.get('integrations.dhis2');
        // Falsy fallback is intentional: empty-string default in the schema means
        // "not configured", so we want to fall through to config in that case.
        const username = dhis2Settings?.username || _config.default.integrations?.dhis2?.username || null;
        const password = await this.getDHIS2Password();
        return {
            username,
            password
        };
    }
    async getDHIS2Password() {
        const sources = [
            {
                label: 'settings secret',
                fetch: ()=>(0, _crypto.getSettingSecret)(this.context.settings, 'integrations.dhis2.password')
            },
            {
                label: 'config secret',
                fetch: ()=>(0, _crypto.getConfigSecret)('integrations.dhis2.password')
            }
        ];
        for (const { label, fetch } of sources){
            try {
                return await fetch();
            } catch (error) {
                if (error instanceof _crypto.SecretNotConfiguredError) continue;
                // Decryption / unexpected errors must not silently fall through to a
                // less-secure source — surface them to operators.
                _logging.log.warn('DHIS2IntegrationProcessor: failed to read password', {
                    source: label,
                    error: error.message
                });
                return null;
            }
        }
        return _config.default.integrations?.dhis2?.password ?? null;
    }
    async postToDHIS2({ reportId, reportCSV }) {
        const { idSchemes, host, backoff } = await this.context.settings.get('integrations.dhis2');
        const { username, password } = await this.getDHIS2Credentials();
        if (!username || !password) {
            // Defensive: run() guards on missing credentials, but postToDHIS2 may be
            // called directly. Failing fast prevents sending "null:null" as a Basic
            // Auth header to DHIS2.
            throw new Error('DHIS2 credentials are not configured');
        }
        const authHeader = Buffer.from(`${username}:${password}`).toString('base64');
        const params = new URLSearchParams({
            ...idSchemes,
            importStrategy: 'CREATE_AND_UPDATE'
        });
        try {
            const response = await (0, _fetchWithRetryBackoff.fetchWithRetryBackoff)(`${host}/api/dataValueSets?${params.toString()}`, {
                fetch: _undici.fetch,
                method: 'POST',
                headers: {
                    'Content-Type': 'application/csv',
                    Accept: 'application/json',
                    Authorization: `Basic ${authHeader}`
                },
                body: reportCSV
            }, {
                ...backoff,
                log: _logging.log
            });
            return await response.json();
        } catch (error) {
            await this.logDHIS2Push({
                reportId,
                status: AUDIT_STATUSES.FAILURE,
                message: error.message
            });
            throw error;
        }
    }
    async processReport(reportId) {
        const { store: { models, sequelize } } = this.context;
        const report = await models.ReportDefinition.findByPk(reportId, {
            include: [
                {
                    model: models.ReportDefinitionVersion,
                    as: 'versions',
                    where: {
                        status: _constants.REPORT_STATUSES.PUBLISHED
                    },
                    order: [
                        [
                            'createdAt',
                            'DESC'
                        ]
                    ],
                    limit: 1,
                    separate: true
                }
            ]
        });
        if (!report) {
            _logging.log.warn(WARNING_LOGS.REPORT_DOES_NOT_EXIST, {
                reportId
            });
            return;
        }
        const reportString = `${report.name} (${reportId})`;
        if (!report.versions || report.versions.length === 0) {
            _logging.log.warn(WARNING_LOGS.REPORT_HAS_NO_PUBLISHED_VERSION, {
                report: reportString
            });
            return;
        }
        _logging.log.info(INFO_LOGS.PROCESSING_REPORT, {
            report: reportString
        });
        const latestVersion = report.versions[0];
        const reportData = await latestVersion.dataGenerator({
            ...this.context,
            sequelize
        }, {}); // We don't support parameters in this task
        const reportCSV = arrayOfArraysToCSV(reportData);
        const { message, httpStatusCode, response: { importCount, conflicts = [] } } = await this.postToDHIS2({
            reportId,
            reportCSV
        });
        if (httpStatusCode === 200) {
            const successLog = await this.logDHIS2Push({
                reportId,
                status: AUDIT_STATUSES.SUCCESS,
                message,
                importCount
            });
            _logging.log.info(INFO_LOGS.SUCCESSFULLY_SENT_REPORT, successLog);
        } else {
            const warningLog = await this.logDHIS2Push({
                reportId,
                status: AUDIT_STATUSES.WARNING,
                message,
                importCount,
                conflicts: conflicts.map((conflict)=>conflict.value)
            });
            _logging.log.warn(WARNING_LOGS.FAILED_TO_SEND_REPORT, warningLog);
            conflicts.forEach((conflict)=>_logging.log.warn(conflict.value));
        }
    }
    async run() {
        const { reportIds, host } = await this.context.settings.get('integrations.dhis2');
        const { enabled } = _config.default.integrations?.dhis2 || {};
        const { username, password } = await this.getDHIS2Credentials();
        if (!enabled || !host || !username || !password || reportIds.length === 0) {
            _logging.log.warn(WARNING_LOGS.INTEGRATION_NOT_CONFIGURED, {
                enabled: !!enabled,
                host: !!host,
                username: !!username,
                password: !!password,
                reportIds: reportIds.length
            });
            return;
        }
        _logging.log.info(INFO_LOGS.SENDING_REPORTS, {
            count: reportIds.length
        });
        for (const reportId of reportIds){
            try {
                await this.processReport(reportId);
            } catch (error) {
                _logging.log.error(ERROR_LOGS.ERROR_PROCESSING_REPORT, {
                    reportId,
                    error
                });
            }
        }
    }
    constructor(context){
        const conf = _config.default.schedules.dhis2IntegrationProcessor;
        const { schedule, jitterTime, enabled } = conf;
        super(schedule, _logging.log, jitterTime, enabled);
        this.config = conf;
        this.context = context;
    }
};

//# sourceMappingURL=DHIS2IntegrationProcessor.js.map