"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SwiftListener = void 0;
exports.createSwiftListener = createSwiftListener;
const path_1 = require("path");
const resourceMatcher_1 = require("./matchers/resourceMatcher");
const inboundParser_1 = require("./parsers/inboundParser");
const mapToCanonical_1 = require("./canonical/mapToCanonical");
const resourceRegistry_1 = require("./resourceRegistry");
const eventStore_1 = require("./store/eventStore");
const inboundAdapters_1 = require("./adapters/inboundAdapters");
const webhookServer_1 = require("./adapters/webhookServer");
const swiftFinTcpAdapter_1 = require("./adapters/swiftFinTcpAdapter");
const dispatchOutcomes_1 = require("./outbound/dispatchOutcomes");
class SwiftListener {
    projectRoot;
    config;
    registry;
    store;
    lastP2pId;
    lastFinId;
    running = false;
    webhookServer;
    tcpServer;
    constructor(projectRoot) {
        this.projectRoot = projectRoot;
        this.config = (0, resourceRegistry_1.loadListenerConfig)(this.projectRoot);
        this.registry = (0, resourceRegistry_1.buildResourceRegistry)(this.projectRoot, this.config);
        this.store = new eventStore_1.EventStore(this.projectRoot, this.config);
    }
    async processMessage(msg) {
        const dedupeKey = this.store.dedupeKey(msg.source, msg.payload);
        if (this.store.isProcessed(dedupeKey))
            return null;
        const parsed = (0, inboundParser_1.parseInbound)(msg.payload);
        let matches = (0, resourceMatcher_1.matchResources)(this.registry, parsed);
        const currency = typeof parsed.parsed.currency === 'string' ? parsed.parsed.currency : undefined;
        if ((0, resourceMatcher_1.currencyMatchesCwAsset)(currency, this.registry)) {
            matches = [
                ...matches,
                {
                    kind: 'cw_symbol',
                    value: currency.toUpperCase(),
                    symbol: currency.toUpperCase(),
                    field: 'parsed.currency',
                },
            ];
        }
        const canonical = matches.length > 0 ? (0, mapToCanonical_1.mapToCanonical)(parsed, dedupeKey) : undefined;
        let event = (0, eventStore_1.buildEvent)(msg.source, parsed.parsed, parsed.format, String(parsed.messageType), matches, canonical, msg.payload, dedupeKey);
        if (event.matched && event.canonical) {
            event = {
                ...event,
                outbound: await (0, dispatchOutcomes_1.dispatchMatchedOutbounds)(this.projectRoot, this.config, this.registry, event),
            };
            console.log(JSON.stringify({
                wired: 'match-outbound',
                eventId: event.id,
                omnlApi: event.outbound?.omnl?.apiPath,
                omnlStatus: event.outbound?.omnl?.apiStatus,
                gateway: event.outbound?.intakeGateway?.gateway,
                gatewayDryRun: event.outbound?.intakeGateway?.dryRun,
                gatewayTx: event.outbound?.intakeGateway?.txHash,
            }));
        }
        this.store.saveEvent(event);
        this.maybeArchiveSource(msg);
        return event;
    }
    maybeArchiveSource(msg) {
        const filePath = msg.metadata?.filePath;
        if (typeof filePath !== 'string')
            return;
        const archiveDir = (0, path_1.resolve)(this.projectRoot, this.config.storage.archiveDir);
        if (msg.source.startsWith('file:') && this.config.adapters.fileInbox.archiveOnProcess) {
            try {
                (0, inboundAdapters_1.archiveInboxFile)(filePath, archiveDir);
            }
            catch {
                // inbox file may already be moved
            }
        }
        if (msg.source.startsWith('as4:') && this.config.adapters.as4Inbox?.archiveOnProcess) {
            try {
                (0, inboundAdapters_1.archiveInboxFile)(filePath, (0, path_1.resolve)(archiveDir, 'as4'));
            }
            catch {
                // ignore
            }
        }
        if (msg.source.startsWith('mail:') && this.config.adapters.mailAttachmentInbox?.archiveOnProcess) {
            try {
                (0, inboundAdapters_1.archiveInboxFile)(filePath, (0, path_1.resolve)(archiveDir, 'mail'));
            }
            catch {
                // ignore
            }
        }
        if (msg.source.startsWith('telex:') && this.config.adapters.telexInbox?.archiveOnProcess) {
            try {
                (0, inboundAdapters_1.archiveInboxFile)(filePath, (0, path_1.resolve)(archiveDir, 'telex'));
            }
            catch {
                // ignore
            }
        }
    }
    async processBatch(sources) {
        const out = [];
        for (const msg of sources) {
            const ev = await this.processMessage(msg);
            if (ev)
                out.push(ev);
        }
        return out;
    }
    async pollFileInbox() {
        return this.processBatch((0, inboundAdapters_1.scanFileInbox)(this.store.inboxPath()));
    }
    async pollAs4Inbox() {
        const dir = this.config.storage.as4InboxDir;
        if (!dir)
            return [];
        return this.processBatch((0, inboundAdapters_1.scanDirectoryInbox)((0, path_1.resolve)(this.projectRoot, dir), 'as4', 'iso20022'));
    }
    async pollMailInbox() {
        const dir = this.config.storage.mailInboxDir;
        if (!dir)
            return [];
        return this.processBatch((0, inboundAdapters_1.scanDirectoryInbox)((0, path_1.resolve)(this.projectRoot, dir), 'mail'));
    }
    async pollTelexInbox() {
        const dir = this.config.storage.telexInboxDir;
        if (!dir)
            return [];
        return this.processBatch((0, inboundAdapters_1.scanDirectoryInbox)((0, path_1.resolve)(this.projectRoot, dir), 'telex', 'ktt'));
    }
    async pollP2pRail() {
        const cfg = this.config.adapters.p2pRail;
        const baseUrl = process.env[cfg.baseUrlEnv] ?? process.env.P2P_BASE_URL ?? '';
        if (!baseUrl)
            return [];
        const messages = await (0, inboundAdapters_1.pollP2pRailTransactions)({
            baseUrl,
            bearerToken: process.env.P2P_BEARER_TOKEN,
            apiKey: process.env.P2P_API_KEY,
            path: cfg.transactionsPath,
            sinceId: this.lastP2pId,
        });
        this.trackLastId(messages, 'p2p');
        return this.processBatch(messages);
    }
    async pollFinGateway() {
        const cfg = this.config.adapters.finGateway;
        const baseUrl = process.env[cfg.baseUrlEnv] ??
            (cfg.fallbackBaseUrlEnv ? process.env[cfg.fallbackBaseUrlEnv] : undefined) ??
            process.env.ALLIANCE_ACCESS_URL ??
            '';
        if (!baseUrl)
            return [];
        const tokenEnv = cfg.authBearerEnv ? process.env[cfg.authBearerEnv] : process.env.FIN_GATEWAY_TOKEN;
        const messages = await (0, inboundAdapters_1.pollFinGatewayMessages)({
            baseUrl,
            bearerToken: tokenEnv,
            path: cfg.messagesPath,
            sinceId: this.lastFinId,
        });
        this.trackLastId(messages, 'fin');
        return this.processBatch(messages);
    }
    trackLastId(messages, prefix) {
        if (messages.length === 0)
            return;
        const last = messages[messages.length - 1];
        const meta = last.metadata;
        const id = String(meta?.id ?? meta?.transactionId ?? meta?.messageId ?? '');
        if (prefix === 'p2p')
            this.lastP2pId = id || this.lastP2pId;
        else
            this.lastFinId = id || this.lastFinId;
    }
    startLongRunningAdapters() {
        const { webhook, swiftFinTcp } = this.config.adapters;
        if (webhook.enabled) {
            const authEnv = webhook.authBearerEnv ?? 'SWIFT_LISTENER_WEBHOOK_TOKEN';
            const secretEnv = webhook.webhookSecretEnv ?? 'HYBX_WEBHOOK_SECRET';
            const webhookSecret = process.env[secretEnv]?.trim();
            this.webhookServer = (0, webhookServer_1.createWebhookServer)({
                port: webhook.port,
                path: webhook.path,
                healthPath: webhook.healthPath,
                authBearer: webhookSecret ? undefined : process.env[authEnv],
                webhookSecret: webhookSecret || undefined,
                webhookSignatureHeader: webhook.webhookSignatureHeader,
                onMessage: async (msg) => {
                    await this.processMessage(msg);
                },
            });
            console.log(JSON.stringify({
                adapter: 'webhook',
                port: webhook.port,
                path: webhook.path,
                healthPath: webhook.healthPath ?? '/health',
            }));
        }
        if (swiftFinTcp.enabled) {
            const port = parseInt(process.env[swiftFinTcp.portEnv] ?? String(swiftFinTcp.defaultPort), 10);
            this.tcpServer = (0, swiftFinTcpAdapter_1.startSwiftFinTcpServer)({
                host: swiftFinTcp.host,
                port,
                onMessage: (msg) => {
                    void this.processMessage(msg);
                },
            });
            console.log(JSON.stringify({ adapter: 'swiftFinTcp', host: swiftFinTcp.host, port }));
        }
    }
    pollIntervals() {
        const { adapters } = this.config;
        const intervals = [];
        if (adapters.fileInbox.enabled)
            intervals.push(adapters.fileInbox.pollIntervalSec);
        if (adapters.p2pRail.enabled)
            intervals.push(adapters.p2pRail.pollIntervalSec);
        if (adapters.finGateway?.enabled)
            intervals.push(adapters.finGateway.pollIntervalSec);
        if (adapters.as4Inbox?.enabled)
            intervals.push(adapters.as4Inbox.pollIntervalSec);
        if (adapters.mailAttachmentInbox?.enabled)
            intervals.push(adapters.mailAttachmentInbox.pollIntervalSec);
        if (adapters.telexInbox?.enabled)
            intervals.push(adapters.telexInbox.pollIntervalSec);
        return intervals.length ? intervals : [60];
    }
    async runPollCycle() {
        const events = [];
        const { adapters } = this.config;
        if (adapters.fileInbox.enabled) {
            events.push(...(await this.pollFileInbox()));
        }
        if (adapters.as4Inbox?.enabled) {
            events.push(...(await this.pollAs4Inbox()));
        }
        if (adapters.mailAttachmentInbox?.enabled) {
            events.push(...(await this.pollMailInbox()));
        }
        if (adapters.telexInbox?.enabled) {
            events.push(...(await this.pollTelexInbox()));
        }
        if (adapters.p2pRail.enabled) {
            try {
                events.push(...(await this.pollP2pRail()));
            }
            catch (e) {
                console.error('P2P rail poll error:', e instanceof Error ? e.message : e);
            }
        }
        if (adapters.finGateway?.enabled) {
            try {
                events.push(...(await this.pollFinGateway()));
            }
            catch (e) {
                console.error('FIN gateway poll error:', e instanceof Error ? e.message : e);
            }
        }
        const matched = events.filter((e) => e.matched);
        if (events.length > 0) {
            console.log(JSON.stringify({
                at: new Date().toISOString(),
                processed: events.length,
                matched: matched.length,
                types: [...new Set(events.map((e) => e.messageType))],
                outbound: matched.filter((e) => e.outbound).length,
            }));
        }
        return events;
    }
    async run(options = {}) {
        if (this.running)
            return;
        this.running = true;
        this.startLongRunningAdapters();
        await this.runPollCycle();
        if (options.once) {
            this.shutdown();
            return;
        }
        const intervalSec = Math.min(...this.pollIntervals());
        const handle = setInterval(() => {
            void this.runPollCycle();
        }, intervalSec * 1000);
        process.on('SIGINT', () => {
            clearInterval(handle);
            this.shutdown();
            process.exit(0);
        });
    }
    shutdown() {
        this.webhookServer?.close();
        this.tcpServer?.close();
        this.running = false;
    }
}
exports.SwiftListener = SwiftListener;
function createSwiftListener(projectRoot) {
    return new SwiftListener(projectRoot);
}
//# sourceMappingURL=listener.js.map