'use strict';

const { Op } = require('sequelize');
const { sequelize, Transaction, UserPlan, Plan, UserBalance, SignalSubscription, User } = require('../models');
const { generateTransactionId } = require('../utils/helpers');
const logger = require('../utils/logger');
const userService = require('../services/userService');

// ─── Helpers ─────────────────────────────────────────────────────────────────

const n = (v) => Number(v ?? 0);
const r2 = (v) => parseFloat(n(v).toFixed(2));

// ─── 1. Auto-cancel stale crypto deposit transactions (> 1 hour old) ─────────

async function cancelStaleCryptoDeposits() {
    try {
        const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);

        const stale = await Transaction.findAll({
            where: {
                source: 'funding',
                kind: 'deposit',
                status: 'initiated',
                createdAt: { [Op.lte]: oneHourAgo },
                txHash: null,          // crypto deposits without a confirmed hash
            },
        });

        for (const tx of stale) {
            try {
                await tx.update({
                    status: 'cancelled',
                    cancelledAt: new Date(),
                    errorMessage: 'Auto-cancelled: deposit not confirmed within 1 hour.',
                });
            } catch (err) {
                logger.error(`[GENERAL] cancelStaleCryptoDeposits txId=${tx.id} — ${err.message}`);
            }
        }
    } catch (err) {
        logger.error(`[GENERAL] cancelStaleCryptoDeposits — ${err.message}`);
    }
}

// ─── 2. Auto-ROI payout for active autoMode plans ────────────────────────────

function computeIntervalMs(roiInterval) {
    const map = {
        hourly: 1000 * 60 * 60,
        daily: 1000 * 60 * 60 * 24,
        weekly: 1000 * 60 * 60 * 24 * 7,
        biweekly: 1000 * 60 * 60 * 24 * 14,
        monthly: 1000 * 60 * 60 * 24 * 30,
        annually: 1000 * 60 * 60 * 24 * 365,
    };
    return map[roiInterval] ?? null;
}

function computeRoiAmount(plan, userPlan) {
    const principal = n(userPlan.amount);
    const roi = n(plan.roi);
    const roiType = plan.roiType ?? 'percent';
    return roiType === 'flat' ? roi : r2((principal * roi) / 100);
}

async function processAutoRoiPayouts() {
    try {
        const userPlans = await UserPlan.findAll({
            where: {
                autoMode: true,
                status: { [Op.in]: ['active', 'paused'] },
            },
            include: [{ model: Plan, as: 'plan' }],
        });

        for (const userPlan of userPlans) {
            try {
                const plan = userPlan.plan;
                if (!plan) continue;

                const intervalMs = computeIntervalMs(plan.roiInterval);
                if (!intervalMs) continue;

                const lastPayout = userPlan.lastPayoutAt ? new Date(userPlan.lastPayoutAt) : new Date(userPlan.activatedAt);
                if (!lastPayout) continue;

                const nextPayout = new Date(lastPayout.getTime() + intervalMs);
                if (new Date() < nextPayout) continue;

                const roiAmount = computeRoiAmount(plan, userPlan);
                if (!roiAmount || roiAmount <= 0) continue;

                await sequelize.transaction(async (t) => {
                    const balance = await UserBalance.findOne({
                        where: { userId: userPlan.userId },
                        lock: t.LOCK.UPDATE,
                        transaction: t,
                    });
                    if (!balance) return;

                    const user = await User.findByPk(userPlan.userId, { transaction: t });
                    if (!user) return;

                    // Credit balance
                    await balance.update(
                        { balance: r2(n(balance.balance) + roiAmount) },
                        { transaction: t }
                    );

                    // Update plan totals
                    await userPlan.update(
                        {
                            totalReturn: r2(n(userPlan.totalReturn) + roiAmount),
                            lastPayoutAt: new Date(),
                        },
                        { transaction: t }
                    );

                    // Audit transaction
                    await Transaction.create(
                        {
                            transactionId: generateTransactionId('ROI'),
                            userId: userPlan.userId,
                            source: 'funding',
                            kind: 'roi',
                            direction: 'credit',
                            toType: 'internal',
                            currency: user.currency ?? 'USD',
                            amount: roiAmount,
                            status: 'completed',
                            planId: userPlan.planId,
                            description: `Auto ROI payout for plan "${plan.name}" (ref: ${userPlan.reference})`,
                            metadata: {
                                userPlanId: userPlan.id,
                                reference: userPlan.reference,
                                interval: plan.roiInterval,
                            },
                        },
                        { transaction: t }
                    );
                });

                userService.notifyUser(userPlan.userId, {
                    title: 'ROI Credited to Your Plan 💰',
                    message: `Your plan "${plan.name}" (Ref: ${userPlan.reference}) received an ROI payout of $${roiAmount.toLocaleString()}.`,
                    type: 'investment',
                    channels: { inApp: true, push: true },
                    priority: 'medium',
                }).catch(() => { });

            } catch (err) {
                logger.error(`[GENERAL] processAutoRoiPayouts userPlanId=${userPlan.id} — ${err.message}`);
            }
        }
    } catch (err) {
        logger.error(`[GENERAL] processAutoRoiPayouts — ${err.message}`);
    }
}

// ─── 3. Expire ended user plans ───────────────────────────────────────────────

async function expireEndedPlans() {
    try {
        const now = new Date();

        const expired = await UserPlan.findAll({
            where: {
                status: 'active',
                endDate: { [Op.lte]: now },
            },
        });

        for (const userPlan of expired) {
            try {
                await userPlan.update({ status: 'ended' });

                userService.notifyUser(userPlan.userId, {
                    title: 'Investment Plan Ended',
                    message: `Your investment plan (Ref: ${userPlan.reference}) has ended.`,
                    type: 'investment',
                    channels: { inApp: true, push: true },
                    priority: 'low',
                }).catch(() => { });

            } catch (err) {
                logger.error(`[GENERAL] expireEndedPlans userPlanId=${userPlan.id} — ${err.message}`);
            }
        }
    } catch (err) {
        logger.error(`[GENERAL] expireEndedPlans — ${err.message}`);
    }
}

// ─── 4. Signal subscription renewal / expiry ─────────────────────────────────

function computeNextPaymentDate(pricePeriod, from = new Date()) {
    const d = new Date(from);
    if (pricePeriod === 'month') d.setMonth(d.getMonth() + 1);
    else if (pricePeriod === 'year') d.setFullYear(d.getFullYear() + 1);
    // lifetime subscriptions never need a next payment
    return d;
}

async function processSignalSubscriptions() {
    try {
        const now = new Date();

        const due = await SignalSubscription.findAll({
            where: {
                status: { [Op.in]: ['active', 'paused'] },
                pricePeriod: { [Op.ne]: 'lifetime' },
                nextPaymentDate: { [Op.lte]: now },
            },
        });

        for (const sub of due) {
            try {
                // No auto-renew — just expire
                if (!sub.autoRenew) {
                    await sub.update({ status: 'expired', endDate: now });

                    userService.notifyUser(sub.userId, {
                        title: 'Signal Subscription Expired',
                        message: `Your signal subscription has expired. Renew to continue receiving signals.`,
                        type: 'subscription',
                        channels: { inApp: true, push: true },
                        priority: 'medium',
                    }).catch(() => { });

                    continue;
                }

                // Auto-renew — check balance
                const price = n(sub.price);

                const balance = await UserBalance.findOne({ where: { userId: sub.userId } });
                if (!balance || n(balance.balance) < price) {
                    // Insufficient balance — expire
                    await sub.update({ status: 'expired', endDate: now });

                    userService.notifyUser(sub.userId, {
                        title: 'Signal Subscription Expired',
                        message: `Your signal subscription could not be renewed due to insufficient balance.`,
                        type: 'subscription',
                        channels: { inApp: true, push: true },
                        priority: 'high',
                    }).catch(() => { });

                    continue;
                }

                // Sufficient balance — renew
                await sequelize.transaction(async (t) => {
                    const lockedBalance = await UserBalance.findOne({
                        where: { userId: sub.userId },
                        lock: t.LOCK.UPDATE,
                        transaction: t,
                    });
                    if (!lockedBalance || n(lockedBalance.balance) < price) throw new Error('Insufficient balance at lock time.');

                    const user = await User.findByPk(sub.userId, { transaction: t });

                    await lockedBalance.update(
                        { balance: r2(n(lockedBalance.balance) - price) },
                        { transaction: t }
                    );

                    const nextPayment = computeNextPaymentDate(sub.pricePeriod, now);

                    await sub.update(
                        {
                            status: 'active',
                            lastPaymentDate: now,
                            nextPaymentDate: nextPayment,
                        },
                        { transaction: t }
                    );

                    await Transaction.create(
                        {
                            transactionId: generateTransactionId('SIG'),
                            userId: sub.userId,
                            source: 'funding',
                            kind: 'signal',
                            direction: 'debit',
                            toType: 'internal',
                            currency: user?.currency ?? 'USD',
                            amount: price,
                            status: 'completed',
                            description: `Signal subscription auto-renewal (providerId: ${sub.providerId})`,
                            metadata: {
                                subscriptionId: sub.id,
                                providerId: sub.providerId,
                                pricePeriod: sub.pricePeriod,
                                nextPayment: nextPayment,
                            },
                        },
                        { transaction: t }
                    );
                });

                userService.notifyUser(sub.userId, {
                    title: 'Signal Subscription Renewed ✅',
                    message: `Your signal subscription has been automatically renewed. Next renewal: ${computeNextPaymentDate(sub.pricePeriod, now).toDateString()}.`,
                    type: 'subscription',
                    channels: { inApp: true, push: true },
                    priority: 'medium',
                }).catch(() => { });

            } catch (err) {
                logger.error(`[GENERAL] processSignalSubscriptions subId=${sub.id} — ${err.message}`);
            }
        }
    } catch (err) {
        logger.error(`[GENERAL] processSignalSubscriptions — ${err.message}`);
    }
}

// ─── Main Cron (every 5 minutes) ─────────────────────────────────────────────

let running = false;

async function runGeneralCron() {
    if (running) return;
    running = true;

    try {
        await Promise.all([
            cancelStaleCryptoDeposits(),
            processAutoRoiPayouts(),
            expireEndedPlans(),
            processSignalSubscriptions(),
        ]);
    } catch (err) {
        logger.error(`[GENERAL] tick failed — ${err.message}`);
    } finally {
        running = false;
    }
}

let cronInterval = null

function start() {
    if (cronInterval) return; // prevent double-start
    const TICK_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
    cronInterval = setInterval(runGeneralCron, TICK_INTERVAL_MS);
}

function stop() {
    clearInterval(cronInterval);
    cronInterval = null;
}

process.on('SIGTERM', stop);
process.on('SIGINT', stop);

module.exports = { start, stop };