import fs from "fs";
import db from "../models/index.js";
import { getRecoveryLogPath } from "../utils/payoutRecoveryLog.js";

const { Merchant, Customer, Transaction, SystemProfit, sequelize } = db;

/**
 * Recovery worker for payouts that 9PSB processed (or was told to process) but whose local
 * database write failed to commit at the time — see src/utils/payoutRecoveryLog.js for how
 * these get logged. Reads the durable file log, reconciles each entry back into the database
 * exactly as the original request would have recorded it, and rewrites the file with only
 * whatever still couldn't be recovered (so successes aren't retried forever, and failures
 * aren't silently dropped).
 *
 * Deliberately a standalone script rather than a setInterval inside the main clustered app —
 * this must run once per cycle, not once per cluster worker.
 */
async function recoverUnrecordedPayouts() {
    console.log("🔄 Starting Unrecorded Payout Recovery Worker...");

    const logFile = getRecoveryLogPath();

    if (!fs.existsSync(logFile)) {
        console.log("✅ No recovery log file found — nothing to recover.");
        process.exit(0);
        return;
    }

    const lines = fs.readFileSync(logFile, "utf8").split("\n").filter((l) => l.trim().length > 0);

    if (lines.length === 0) {
        console.log("✅ Recovery log is empty — nothing to recover.");
        process.exit(0);
        return;
    }

    console.log(`🔎 Found ${lines.length} unrecorded payout(s) to process.`);

    const stillUnrecovered = [];
    let recovered = 0;

    for (const line of lines) {
        let entry;
        try {
            entry = JSON.parse(line);
        } catch (parseError) {
            console.error("❌ Dropping unparsable recovery log line:", line);
            continue;
        }

        try {
            // If a Transaction with this reference already exists, it was already reconciled
            // somehow (e.g. the original commit actually succeeded despite throwing) — nothing to do.
            const existing = await Transaction.findOne({ where: { transaction_id: entry.transactionReference } });
            if (existing) {
                console.log(`⏭  ${entry.transactionReference} already exists in the database — skipping.`);
                recovered++;
                continue;
            }

            const amount = parseFloat(entry.amount);
            const fee = parseFloat(entry.fee) || 0;
            const stampDuty = parseFloat(entry.stampDuty) || 0;
            const totalAmount = amount + fee + stampDuty;

            await sequelize.transaction(async (t) => {
                if (entry.customerId) {
                    // Customer-initiated payout: customer paid `amount`, merchant paid fee + stamp duty.
                    const customer = await Customer.findByPk(entry.customerId, { transaction: t, lock: true });
                    if (customer) {
                        await customer.decrement("balance", { by: amount, transaction: t });
                    }
                    const merchant = await Merchant.findByPk(entry.merchantId, { transaction: t, lock: true });
                    if (merchant && fee + stampDuty > 0) {
                        await merchant.decrement("balance", { by: fee + stampDuty, transaction: t });
                    }
                } else {
                    // Merchant's own payout: merchant paid amount + fee + stamp duty entirely.
                    const merchant = await Merchant.findByPk(entry.merchantId, { transaction: t, lock: true });
                    if (merchant) {
                        await merchant.decrement("balance", { by: totalAmount, transaction: t });
                    }
                }

                await Transaction.create({
                    merchantId: entry.merchantId,
                    customerId: entry.customerId || null,
                    transaction_id: entry.transactionReference,
                    type: "debit",
                    class: "payout",
                    amount,
                    fee,
                    stampDuty,
                    total_amount: totalAmount,
                    status: entry.status, // "success" or "pending" — exactly as originally determined
                    currency: entry.currency || "NGN",
                    info: `Payout to ${entry.accountName} (${entry.accountNumber}) [Recovered from local database failure at ${entry.loggedAt} — original error: ${entry.originalError}]`,
                }, { transaction: t });

                if (entry.status === "success") {
                    await SystemProfit.create({
                        amount: fee,
                        source: "payout_fee",
                        transactionId: entry.transactionReference,
                        merchantId: entry.merchantId,
                    }, { transaction: t });
                }
            });

            console.log(`✅ Recovered ${entry.transactionReference} (status: ${entry.status}).`);
            recovered++;
        } catch (err) {
            console.error(`❌ Failed to recover ${entry.transactionReference || "(unknown reference)"}:`, err.message);
            stillUnrecovered.push(line);
        }
    }

    fs.writeFileSync(logFile, stillUnrecovered.length > 0 ? stillUnrecovered.join("\n") + "\n" : "");

    console.log(`✅ Recovery cycle complete. Recovered ${recovered}, still unrecovered: ${stillUnrecovered.length}.`);
    process.exit(0);
}

recoverUnrecordedPayouts().catch((err) => {
    console.error("❌ Critical error in Recovery Worker:", err);
    process.exit(1);
});
